Issue 26477: typing forward references and module attributes (original) (raw)
Forward references to a module can fail, if the module doesn't yet have the required object. The "forward references" section names circular dependencies as one use for forward references, but the following example fails:
$ cat test/init.py from .a import A from .b import B $ cat test/a.py import typing from . import b
class A: def foo(self: 'A', bar: typing.Union['b.B', None]): pass $ cat test/b.py import typing from . import a
class B: def spam(self: 'B', eggs: typing.Union['a.A', None]): pass $ bin/python -c 'import test' Traceback (most recent call last): File "", line 1, in File "/Users/mjpieters/Development/venvs/stackoverflow-3.5/test/init.py", line 1, in from .a import A File "/Users/mjpieters/Development/venvs/stackoverflow-3.5/test/a.py", line 2, in from . import b File "/Users/mjpieters/Development/venvs/stackoverflow-3.5/test/b.py", line 4, in class B: File "/Users/mjpieters/Development/venvs/stackoverflow-3.5/test/b.py", line 5, in B def spam(self: 'B', eggs: typing.Union['a.A', None]): File "/Users/mjpieters/Development/Library/buildout.python/parts/opt/lib/python3.5/typing.py", line 537, in getitem dict(self.dict), parameters, _root=True) File "/Users/mjpieters/Development/Library/buildout.python/parts/opt/lib/python3.5/typing.py", line 494, in new for t2 in all_params - {t1} if not isinstance(t2, TypeVar)): File "/Users/mjpieters/Development/Library/buildout.python/parts/opt/lib/python3.5/typing.py", line 494, in for t2 in all_params - {t1} if not isinstance(t2, TypeVar)): File "/Users/mjpieters/Development/Library/buildout.python/parts/opt/lib/python3.5/typing.py", line 185, in subclasscheck self._eval_type(globalns, localns) File "/Users/mjpieters/Development/Library/buildout.python/parts/opt/lib/python3.5/typing.py", line 172, in _eval_type eval(self.forward_code, globalns, localns), File "", line 1, in AttributeError: module 'test.a' has no attribute 'A'
The forward reference test fails because only NameError exceptions are caught, not AttributeError exceptions.
I wonder why they forward references are evaluated at all at this point.
The Union type tries to reduce the set of allowed types by removing any subclasses (so Union[int, bool] becomes Union[int] only). That's all fine, but it should not at that point fail if a forward reference is not available yet.
Arguably, the except NameError there should be converted to a except Exception, since forward references are supposed to be a valid Python expression [...] and it should evaluate without errors once the module has been fully loaded. (from the PEP); anything goes, and thus any error goes until the module is loaded.