-
-
Save JelleZijlstra/f9589daadb0d73f64a88f3292d76c808 to your computer and use it in GitHub Desktop.
Demo for an `__annotations__` solution
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from collections.abc import Mapping | |
class _AnnotationsDescriptor(Mapping): | |
def __init__(self, owner: type, name: str): | |
self._annotations_cache = None | |
self._owner = owner | |
self._name = name | |
# Must act as a descriptor if accessed via `.` | |
def _materialize_annotations(self) -> None: | |
if self._annotations_cache is None: | |
self._annotations_cache = self._owner.__annotate__(1) | |
def __get__(self, obj, cls=None) -> dict: | |
if obj is not None: | |
raise AttributeError( | |
f"{type(obj).__name__!r} object has no attribute {self._name!r}" | |
) | |
assert cls is self._owner | |
self._materialize_annotations() | |
return self._annotations_cache | |
# Must act as a mapping if accessed via `__dict__`: | |
def __getitem__(self, key: str): | |
self._materialize_annotations() | |
return self._annotations_cache[key] | |
def __iter__(self): | |
self._materialize_annotations() | |
yield from self._annotations_cache | |
def __len__(self) -> int: | |
self._materialize_annotations() | |
return len(self._annotations_cache) | |
class Object: | |
"""Pretend this is how builtins.object would work""" | |
def __init_subclass__(cls): | |
cls.annotations = _AnnotationsDescriptor(cls, "annotations") | |
class Meta(Object, type): | |
x: int | |
class Foo(Object, metaclass=Meta): | |
y: str | |
class Bar(Object, metaclass=Meta): | |
pass | |
def test(): | |
assert Meta.annotations == {"x": int} | |
assert Meta.annotations["x"] is int | |
assert Meta.annotations.get("x", bytes) is int | |
assert Foo.annotations == {"y": str} | |
assert Bar.annotations == {} | |
try: | |
Foo().annotations | |
except AttributeError: | |
pass | |
else: | |
assert False, "AttributeError should have been raised" | |
print("Tests all passed") | |
if __name__ == "__main__": | |
test() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment