• Any way to "subclass" typing.Annotated?

    From Ian Pilcher@arequipeno@gmail.com to comp.lang.python on Tue Jan 28 16:02:47 2025
    From Newsgroup: comp.lang.python

    (Note: I have mail delivery disabled for this list and read it through
    GMane. Please copy me on any responses, so that I can respond with
    proper threading.)

    From the things that I probably shouldn't spend my free time on
    department ...

    As background, I'm working on a project that is going to involve a bunch
    of abstract classes and dynamic types, and I've found that Python's
    existing abstract class implementation leaves a lot to be desired,
    particularly the inability to create abstract class variables and class methods. Having been seduced by the Siren song of Python's flexibility,
    I've been rolling my own implementation.

    Now to my question.

    I'm currently using annotations to create abstract class variables, for example:

    class Foo(object, metaclass=AbstractType):

    acv: Annotated[int, abstract]

    ('abstract' is simply a unique "flag" object.)

    This works just fine, but it's somewhat un-idiomatic. What I'd like to
    be able to do is create my own type, so that I could do something like
    this:

    class Foo(object, metaclass=AbstractType):

    acv: AbstractClassVariable[int]

    Essentially I'd like to create "subclass" of typing.Annotated that
    always sets the metadata to 'abstract'. Thus far, I haven't found a
    way to do this, as typing.Annotated can't be subclassed.

    Anyone have any ideas?
    --
    ========================================================================
    If your user interface is intuitive in retrospect ... it isn't intuitive ========================================================================

    --- Synchronet 3.20c-Linux NewsLink 1.2
  • From ram@ram@zedat.fu-berlin.de (Stefan Ram) to comp.lang.python on Wed Jan 29 13:03:55 2025
    From Newsgroup: comp.lang.python

    Ian Pilcher <arequipeno@gmail.com> wrote or quoted:
    Essentially I'd like to create "subclass" of typing.Annotated that
    always sets the metadata to 'abstract'. Thus far, I haven't found a
    way to do this, as typing.Annotated can't be subclassed.

    Alright, so here's the deal: you're right that typing.Annotated
    is kind of stubborn and won't let you subclass it. But don't
    worry, there's a way to finesse this. You can rig up something
    that works just as well without having to wrestle Python's
    type system into submission. Let me walk you through it.

    Option 1: Factory Function for AbstractClassVariable

    Think of this as building a little factory that cranks out Annotated
    types with your custom abstract flag baked in. Here's how it looks:

    Python

    from typing import Annotated, TypeVar, Any

    # This is your "abstract" flag. It's just a unique object.
    abstract = object()

    # A generic type variable to keep things flexible.
    T = TypeVar("T")

    # The factory function
    def AbstractClassVariable(type_: T) -> Any:
    return Annotated[type_, abstract]

    Now, when you're writing your class, you just call this factory
    like so:

    Python

    class Foo(object, metaclass=AbstractType):
    acv: AbstractClassVariable[int]

    Boom. Clean, simple, and gets the job done without any drama.

    Option 2: Custom Wrapper Class

    If you're more into the idea of having something that looks
    like a class but still does the same thing, you can roll your
    own wrapper class. Here's what that might look like:

    Python

    from typing import Annotated, TypeVar, Generic

    # Again, your trusty "abstract" flag.
    abstract = object()

    # Same type variable as before.
    T = TypeVar("T")

    class AbstractClassVariable(Generic[T]):
    def __class_getitem__(cls, item: T) -> Any:
    return Annotated[item, abstract]

    With this setup, you can write your class exactly the way you
    wanted to in the first place:

    Python

    class Foo(object, metaclass=AbstractType):
    acv: AbstractClassVariable[int]

    It's basically the same as Option 1 but with a bit more flair.
    What's Going On Here?

    - Factory Function: This is just a shortcut to make sure
    every time you create an abstract class variable, it
    automatically comes with your abstract flag attached.

    - Custom Wrapper Class: By overriding __class_getitem__,
    we're letting Python treat AbstractClassVariable[int] as
    shorthand for Annotated[int, abstract]. It's like hacking
    the system without actually breaking anything.

    Both options are solid—pick whichever one vibes better with your
    style.

    Enforcing Abstract Class Variables

    Now, if you want to make sure subclasses actually define
    these abstract class variables (because let's face it,
    someone will forget), you'll need to tweak your metaclass
    a bit. Here's an example:

    Python

    class AbstractType(type):
    def __init__(cls, name, bases, namespace):
    super().__init__(name, bases, namespace)
    for attr_name, attr_type in namespace.get("__annotations__", {}).items():
    if isinstance(attr_type, Annotated) and abstract in getattr(attr_type, "__metadata__", []):
    if not hasattr(cls, attr_name):
    raise TypeError(f"Class {cls.__name__} must define abstract class variable '{attr_name}'.")

    This basically scans through all the annotated variables in
    your class and checks if they've got the abstract flag. If they
    do and no one bothered to define them in a subclass? Game over.

    Example in Action

    Here's how it plays out:

    Python

    class Foo(object, metaclass=AbstractType):
    acv: AbstractClassVariable[int]

    # This will blow up because Bar doesn't define 'acv'.
    class Bar(Foo):
    pass

    # This works because Baz actually defines 'acv'.
    class Baz(Foo):
    acv = 42

    So yeah, that's the gist. It's flexible enough to fit into
    whatever setup you've got going on and keeps things Pythonic
    without veering off into uncharted territory.

    Let me know if anything feels off or if I missed something!


    --- Synchronet 3.20c-Linux NewsLink 1.2
  • From ram@ram@zedat.fu-berlin.de (Stefan Ram) to comp.lang.python on Wed Jan 29 13:34:51 2025
    From Newsgroup: comp.lang.python

    ram@zedat.fu-berlin.de (Stefan Ram) wrote or quoted:
    Alright, so here's the deal: you're right that typing.Annotated

    Alright, so here's the deal with "typing.Annotated" in Python.

    It's like adding extra toppings to your In-N-Out burger - you're
    not just saying what type of meat you want, you're throwing on
    some secret sauce too. Imagine you're at a Coachella for coders.

    Here's what typing.Annotated brings to the party:

    - Type Declaration: You're still telling Python what kind of
    data you're working with, like saying "this variable is as
    integer as the number of surfers at Huntington Beach."

    - Extra Info: But now you can slap on some bonus details,
    like how you'd stick a "I climbed Half Dome" sticker on
    your Nalgene.

    - Flexibility: This extra info can be pretty much anything -
    like how you can put literally anything on avocado toast
    and Californians will eat it up.

    Here's a quick example, as easy as parallel parking on Lombard
    Street:

    Python

    from typing import Annotated

    # Plain Jane version
    age: int

    # Fancy pants version
    age: Annotated[int, "How many times you've seen the Hollywood sign"]

    Both lines are saying age is an integer, but the Annotated one
    is like adding a little note that your hipster friend's artisanal
    documentation tool might dig.

    Just a heads up, you might need to grab the typing_extensions
    module (like picking up some craft IPA from your local microbrewery)
    to use Annotated in older Python versions. It's fresher than
    the produce at Berkeley Bowl in the newer versions.

    Bottom line, typing.Annotated is like the GPS for your code
    - it doesn't change where you're going, but it sure makes the
    journey clearer for everyone involved. It's especially clutch
    when you're working on projects bigger than Silicon Valley egos.


    --- Synchronet 3.20c-Linux NewsLink 1.2
  • From ram@ram@zedat.fu-berlin.de (Stefan Ram) to comp.lang.python on Wed Jan 29 13:45:30 2025
    From Newsgroup: comp.lang.python

    ram@zedat.fu-berlin.de (Stefan Ram) wrote or quoted:
    # The factory function
    def AbstractClassVariable(type_: T) -> Any:
    return Annotated[type_, abstract]

    Alright, so now I've got something here that actually compiles!

    Python

    from typing import Annotated, TypeVar, Any, Generic

    # This is your "abstract" flag. It's just a unique object.
    abstract = object()

    # A generic type variable to keep things flexible.
    T = TypeVar("T")

    # Define AbstractClassVariable as a class instead of a function
    class AbstractClassVariable(Generic[T]):
    def __class_getitem__(cls, item):
    return Annotated[item, abstract]

    class AbstractType(type):
    def __new__(cls, name, bases, namespace):
    for key, value in namespace.items():
    if isinstance(value, type(Annotated)):
    if abstract in value.__metadata__:
    raise TypeError(f"Abstract class variable '{key}' must be defined")

    return super().__new__(cls, name, bases, namespace)

    class Foo(object, metaclass=AbstractType):
    acv: AbstractClassVariable[int]


    --- Synchronet 3.20c-Linux NewsLink 1.2
  • From ram@ram@zedat.fu-berlin.de (Stefan Ram) to comp.lang.python on Wed Jan 29 13:48:00 2025
    From Newsgroup: comp.lang.python

    ram@zedat.fu-berlin.de (Stefan Ram) wrote or quoted:
    Option 2: Custom Wrapper Class

    To compile, this should read:

    Python

    from typing import Annotated, TypeVar, Generic, Any

    # Your trusty "abstract" flag, chillin' like it's at Venice Beach
    abstract = object()

    # Type variable, as constant as the NorCal fog
    T = TypeVar("T")

    class AbstractClassVariable(Generic[T]):
    def __class_getitem__(cls, item: T) -> Any:
    return Annotated[item, abstract]


    --- Synchronet 3.20c-Linux NewsLink 1.2
  • From Fabien LUCE@fabienluce@gmail.com to comp.lang.python on Fri Jan 31 11:41:22 2025
    From Newsgroup: comp.lang.python

    Maybe you'd better use descriptors?

    On Tue, 28 Jan 2025 at 23:03, Ian Pilcher via Python-list < python-list@python.org> wrote:

    (Note: I have mail delivery disabled for this list and read it through GMane. Please copy me on any responses, so that I can respond with
    proper threading.)

    From the things that I probably shouldn't spend my free time on
    department ...

    As background, I'm working on a project that is going to involve a bunch
    of abstract classes and dynamic types, and I've found that Python's
    existing abstract class implementation leaves a lot to be desired, particularly the inability to create abstract class variables and class methods. Having been seduced by the Siren song of Python's flexibility,
    I've been rolling my own implementation.

    Now to my question.

    I'm currently using annotations to create abstract class variables, for example:

    class Foo(object, metaclass=AbstractType):

    acv: Annotated[int, abstract]

    ('abstract' is simply a unique "flag" object.)

    This works just fine, but it's somewhat un-idiomatic. What I'd like to
    be able to do is create my own type, so that I could do something like
    this:

    class Foo(object, metaclass=AbstractType):

    acv: AbstractClassVariable[int]

    Essentially I'd like to create "subclass" of typing.Annotated that
    always sets the metadata to 'abstract'. Thus far, I haven't found a
    way to do this, as typing.Annotated can't be subclassed.

    Anyone have any ideas?

    --
    ========================================================================
    If your user interface is intuitive in retrospect ... it isn't intuitive ========================================================================

    --
    https://mail.python.org/mailman/listinfo/python-list

    --- Synchronet 3.20c-Linux NewsLink 1.2