This code:
#!/usr/bin/env python from typing import Optional, Type class Foo(object): pass class Bar(Foo): pass def test_me() -> Optional[Type[Foo]]: print("Hi there!") return Bar if __name__ == "__main__": test_me()
will raise TypeError
on 3.5.2:
Traceback (most recent call last): File "./test.py", line 11, in <module> def test_me() -> Optional[Type[Foo]]: File "/Users/mnot/.pyenv/versions/3.5.2/lib/python3.5/typing.py", line 649, in __getitem__ return Union[arg, type(None)] File "/Users/mnot/.pyenv/versions/3.5.2/lib/python3.5/typing.py", line 552, in __getitem__ dict(self.__dict__), parameters, _root=True) File "/Users/mnot/.pyenv/versions/3.5.2/lib/python3.5/typing.py", line 512, in __new__ for t2 in all_params - {t1} if not isinstance(t2, TypeVar)): File "/Users/mnot/.pyenv/versions/3.5.2/lib/python3.5/typing.py", line 512, in <genexpr> for t2 in all_params - {t1} if not isinstance(t2, TypeVar)): File "/Users/mnot/.pyenv/versions/3.5.2/lib/python3.5/typing.py", line 1077, in __subclasscheck__ if super().__subclasscheck__(cls): File "/Users/mnot/.pyenv/versions/3.5.2/lib/python3.5/abc.py", line 225, in __subclasscheck__ for scls in cls.__subclasses__(): TypeError: descriptor '__subclasses__' of 'type' object needs an argument
whereas it runs fine on 3.6. Same problem if I spell out Optional as Union[None, Type[Foo]]
.
Is there any workaround for 3.5.2, while still accurately annotating the return type?
Advertisement
Answer
This is a bug in Python 3.5.2.
Optional[cls]
is a wrapper for Union[cls, type(None)]
, which uses __subclasses__()
to establish whether one class is a subclass of another.
However, Type
is a subclass of type
in Python 3.5.2, which means that
Union[Type[anything], anything_else]
will eventually call
type.__subclasses__()
… which is a problem, because type
is a metaclass, and so expects to be called with the class whose subclasses are being sought, in exactly the same way that calling an instance method on a regular class requires you to supply an instance of itself, e.g. str.upper('foo')
.
The problem is fixed in Python 3.5.3 (and, as you’ve noticed, 3.6) by making Type
no longer a subclass of type
.