Deutsch   English   Français   Italiano  
<subclass-20250129135836@ram.dialup.fu-berlin.de>

View for Bookmarking (what is this?)
Look up another Usenet article

Path: ...!fu-berlin.de!uni-berlin.de!not-for-mail
From: ram@zedat.fu-berlin.de (Stefan Ram)
Newsgroups: comp.lang.python
Subject: Re: Any way to "subclass" typing.Annotated?
Date: 29 Jan 2025 13:03:55 GMT
Organization: Stefan Ram
Lines: 128
Expires: 1 Jan 2026 11:59:58 GMT
Message-ID: <subclass-20250129135836@ram.dialup.fu-berlin.de>
References: <mailman.94.1738101773.2912.python-list@python.org>
Mime-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
X-Trace: news.uni-berlin.de qKBWXegX1NmP1leA96rnAAzvyoBnxLdHCfvgTwfdiTke8n
Cancel-Lock: sha1:FMqsu4mJiuDv+UISv1NbrlUu88U= sha256:Tmtu6a96fh2gJstIQsVevk/cissEC8frMi3jFLZadYY=
X-Copyright: (C) Copyright 2025 Stefan Ram. All rights reserved.
	Distribution through any means other than regular usenet
	channels is forbidden. It is forbidden to publish this
	article in the Web, to change URIs of this article into links,
        and to transfer the body without this notice, but quotations
        of parts in other Usenet posts are allowed.
X-No-Archive: Yes
Archive: no
X-No-Archive-Readme: "X-No-Archive" is set, because this prevents some
	services to mirror the article in the web. But the article may
	be kept on a Usenet archive server with only NNTP access.
X-No-Html: yes
Content-Language: en-US
Bytes: 5527

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!