Closed larryhastings closed 3 years ago
What if the annotation is being used inside a function:
class X:
Alias = int
def outer(self):
x: Alias
def inner(y: Alias): ...
On the one hand, maybe this should work because class-scoped type aliases are one of the main use cases for this sort of thing, and if you want to use the alias in the method's own annotation, you may also want to use it in a local variable. On the other hand, this doesn't work under current vanilla Python, so there's less of a need to support it.
Your sketch for how to fix the non-nested case was enough to get at least simple examples to work; I put up my code in #5. Undoubtedly there are edge cases that fail.
The desired semantics for PEP 649 should behave like vanilla (what I call "stock") semantics in Python 3.9, except for one super-power: an annotation can refer to things defined after it in the file. If you're defining an annotation on X, pretend that you could magically temporarily move X to the end of the scope it's defined in (module / class / function) just when evaluating its annotations.
So I don't think 649 should make the annotations on X.outer.x
and X.outer.inner.y
in your example viable.
Makes sense. Here's another use case someone just brought up: https://bugs.python.org/issue43746.
from ... import losses
class A:
# AttributeError: 'Losses' object has no attribute 'Losses' (under stock semantics)
losses: losses.Losses = losses.Losses()
This makes sense looking at the disassembly: we first set losses
in the class namespace, then evaluate the annotation. Under PEP 649 semantics that's also the sensible thing to do. However, type checkers currently accept this code.
Theoretically I should be interested in edge cases like this. But it's hard to see how defining an annotation in terms of the name it's annotating is valid Python.
Theoretically I should be interested in edge cases like this. But it's hard to see how defining an annotation in terms of the name it's annotating is valid Python.
I think this is the crux of the problem. Are annotations supposed to be valid Python, or not? If not, then let's just admit it and say stringized annotations are the only way forward and that PEP 649 has no future.
Personally, I think annotations should be valid Python code and I'd like to see PEP 649 accepted.
I'm trying to get feature parity with existing (non-stringized) annotations. Consider this example:
That code works fine in Python 3.9. But if you try it in the
co_annotations
branch and uncomment the first line it no longer works.I'm pretty sure it's because
__co_annotations__
is a function, and the function is being defined inside a class, and Python doesn't let functions defined inside of classes see the class namespace. That's why it works ifco_annotations
isn't turned on; the__annotations__
dict is generated from within the class namespace, so the lookup works fine.But, well. co_annotations functions are special, aren't they? And we want them to see fields defined in the class namespace. In fact we want them to be closures, keeping references to those values alive.
I'm posting this here in case anybody seeing this (Guido, Jelle) can suggest how to fix this. My best guess so far is that I need to add a fourth
_Py_block_ty
tosymtable.h
for co_annotations functions. This would be a nested block inside aClassBlock
that behaved like aFunctionBlock
except it was allowed to see the definitions inside theClassBlock
. My fear is that there might be baked-in assumptions inside classes that e.g. aClassBlock
would never ever contain a... cell variable? Is that right? I'm still mostly working in the dark when it comes to how closures are implemented in Python.