Reputation: 727
I need to declare global variables that have "complex" type and should not be instantiated at import time. In Python 3.6+, I can omit initialization, for example:
log: logging.Logger
pollset: select.poll
I need to make the code compatible with Python 3.5. I can use comment type annotations:
log = ... # type: logging.Logger
pollset = ... # type: select.poll
but then I have to provide initial value. This is not a problem at runtime, assigning the initial value of None
or ...
will do. But any of those trigger mypy typecheck error:
myprog.py:19: error: Incompatible types in assignment (expression has type "ellipsis", variable has type "Logger")
Of course I could use Optional
type to allow initializing to None
, but then type checking would be weakened. For instance, assigning None
value to the variable elsewhere in the code is illegal, but it would not be caught.
Is there an accepted way to use strong type checking of a variable in a way that is compatible with Python 3.5?
Upvotes: 5
Views: 3614
Reputation: 28751
The special treatment of None
for PEP 484 was decided in python/typing#61. Sadly, the type checkers (I tried mypy and pyre) don't implement this. The problem is further discussed in python/typing#81.
There are workarounds that can be used
None
, or ellipsis, to the correct dynamic type. This will placate both mypy and pyre.self.process: subprocess.Popen = cast(subprocess.Popen, None)
self.process: subprocess.Popen = cast(subprocess.Popen, ...)
self.process: subprocess.Popen = None # type: ignore
Optional[...]
, and then do checks for None
on every accessself.process: Optional[subprocess.Popen] = None
Upvotes: 0
Reputation: 50066
According to PEP 484, assigning None
is correct.
log = None # type: logging.Logger
Note that mypy
only allows this at class scope. However, you can declare a type and tell mypy
to ignore the assignment itself (since mypy
0.700).
log = None # type: logging.Logger # type: ignore
Also, you can use a .pyi
stub file regardless of Python version.
# my_lib.pyi
log: logging.Logger
However, in non-stub code for versions of Python 3.5 and earlier there is a special case:
from typing import IO stream = None # type: IO[str]
Type checkers should not complain about this (despite the value None not matching the given type), nor should they change the inferred type to Optional[...] (despite the rule that does this for annotated arguments with a default value of None). The assumption here is that other code will ensure that the variable is given a value of the proper type, and all uses can assume that the variable has the given type.
Upvotes: 4
Reputation: 63978
One technique you could do is create a dummy variable with type Any
, then use that instead of setting your variables to ...
or None
. For example:
from typing import Any
_bogus = None # type: Any
log = _bogus # type: logging.Logger
pollset = _bogus # type: select.poll
However, this solution isn't perfect. With variable annotations, we avoided actually creating assigning a value to these variables, so attempting to use log
before it's instantiated would result in a NameError at runtime.
However, with this approach, we'd instead get None
, which contradicts our declared type.
Maybe this is ok for your use case, but if it isn't, we can get something closer to the variable annotations behavior by sticking these inside of a if TYPE_CHECKING
block:
from typing import Any, TYPE_CHECKING
if TYPE_CHECKING:
_bogus = None # type: Any
log = _bogus # type: logging.Logger
pollset = _bogus # type: select.poll
The TYPE_CHECKING
variable is always False at runtime, but treated as if it were True by type-checkers like mypy.
(Doing if False
also works. It's a common-enough convention that mypy directly supports that as an alternative to using TYPE_CHECKING
.)
Upvotes: 1