Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Optional[Type[Foo]] raises TypeError in Python 3.5.2

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?

like image 385
Mark Nottingham Avatar asked Mar 22 '17 04:03

Mark Nottingham


1 Answers

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.

like image 117
Zero Piraeus Avatar answered Oct 03 '22 22:10

Zero Piraeus