What is the proper type signature for __exit__
? I have the following:
from types import TracebackType
from typing import Optional, Type
class Foo:
def __enter__(self) -> 'Foo':
return self
def __exit__(self, exc_type: Optional[Type[BaseException]],
exc_value: Optional[BaseException],
traceback: Optional[TracebackType]) -> bool:
return False
On recent mypy (0.560) this typechecks with --strict
(and I have moderate faith in this signature, because I stole it from the innards of the typeshed).
When this script is run with python 3.6, as expected nothing happens. But when run with 3.5.2, we get an exception:
Traceback (most recent call last):
File "/home/student/mypy_test/test.py", line 4, in <module>
class Foo: #(ContextManager['Foo']):
File "/home/student/mypy_test/test.py", line 8, in Foo
def __exit__(self, exc_type: Optional[Type[BaseException]],
File "/usr/lib/python3.5/typing.py", line 649, in __getitem__
return Union[arg, type(None)]
File "/usr/lib/python3.5/typing.py", line 552, in __getitem__
dict(self.__dict__), parameters, _root=True)
File "/usr/lib/python3.5/typing.py", line 512, in __new__
for t2 in all_params - {t1} if not isinstance(t2, TypeVar)):
File "/usr/lib/python3.5/typing.py", line 512, in <genexpr>
for t2 in all_params - {t1} if not isinstance(t2, TypeVar)):
File "/usr/lib/python3.5/typing.py", line 1077, in __subclasscheck__
if super().__subclasscheck__(cls):
File "/home/student/.local/share/virtualenvs/sf_cs328-crowdsourced-QAuuIxFA/lib/python3.5/abc.py", line 225, in __subclasscheck__
for scls in cls.__subclasses__():
TypeError: descriptor '__subclasses__' of 'type' object needs an argument
If you remove parameters until the exception disappears, we find that the problem type is the first: exc_type: Optional[Type[BaseException]]
.
Note: To get it to complain if the type signature does not match (when run with mypy), you need to change class Foo:
to class Foo(ContextManager['Foo'])
. I didn't do this in the snippet, because typing
in Python 3.5.2 is missing Coroutine
, Awaitable
, ContextManager
, etc (this is the version in LTS releases of old-ish distros). I wrote about a workaround for this here: https://stackoverflow.com/a/49952293/568785. So I guess, the full reproducible example is:
# Workaround for ContextManager missing in 3.5.2 typing
from typing import Any, TypeVar, TYPE_CHECKING
try:
from typing import ContextManager
except ImportError:
class _ContextManager:
def __getitem__(self, index: Any) -> None:
return type(object())
if not TYPE_CHECKING:
ContextManager = _ContextManager()
# The actual issue:
from types import TracebackType
from typing import Optional, Type
class Foo:
def __enter__(self) -> 'Foo':
return self
def __exit__(self, exc_type: Optional[Type[BaseException]],
exc_value: Optional[BaseException],
traceback: Optional[TracebackType]) -> bool:
return False
I've verified that not inheriting ContextManager
for running in Python 3.5.2 still produces the error (so this exception isn't a product of this hack, it's a product of the 3.5.2 runtime's typing library not liking the signature of __exit__
).
Presumably, this is another bug in Python 3.5's typing
library. Is there a sane way to work around this?
PEP 484, which provides a specification about what a type system should look like in Python3, introduced the concept of type hints.
A TypedDict is a new kind of type recognized by Python typecheckers such as mypy. It describes a structured dictionary/map with an expected set of named string keys mapped to values of particular expected types. Such structures are ubiquitous when exchanging JSON data, which is common for Python web applications to do.
Type hints help you build and maintain a cleaner architecture. The act of writing type hints forces you to think about the types in your program. While the dynamic nature of Python is one of its great assets, being conscious about relying on duck typing, overloaded methods, or multiple return types is a good thing.
Use Optional to indicate that an object is either one given type or None . For example: from typing import Dict, Optional, Union dict_of_users: Dict[int, Union[int,str]] = { 1: "Jerome", 2: "Lewis", 3: 32 } user_id: Optional[int] user_id = None # valid user_id = 3 # also vald user_id = "Hello" # not valid!
A ugly workaround that I'm using for now, is use TYPE_CHECKING
to determine if I should just fake the type:
from typing import Type, TYPE_CHECKING
if TYPE_CHECKING:
BaseExceptionType = Type[BaseException]
else:
BaseExceptionType = bool # don't care, as long is it doesn't error
Then you can do:
def __exit__(self, exc_type: Optional[BaseExceptionType],
exc_value: Optional[BaseException],
traceback: Optional[TracebackType]) -> bool:
return False
Verified against Python 3.5.2 and mypy 0.560.
This of course breaks any RTTI, but AFAIK RTTI is part of a PEP that didn't land (experimentally) until 3.6 or 3.7. This does break typing.get_type_hints()
though, obviously.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With