Is this function type-annotated correctly?
import subprocess
from os import PathLike
from typing import Union, Sequence, Any
def run(shell_command: Union[bytes, str, Sequence[Union[bytes, str, PathLike]]], **subprocess_run_kwargs: Any) -> int:
return subprocess.run(shell_command, check=True, shell=True, **subprocess_run_kwargs).returncode
I'm guessing it's not, because I'm getting:
he\other.py:6: error: Missing type parameters for generic type
To get the same error, then save the above code in other.py
, and then:
$ pip install mypy
$ mypy --strict other.py
Type annotations — also known as type signatures — are used to indicate the datatypes of variables and input/outputs of functions and methods. In many languages, datatypes are explicitly stated. In these languages, if you don't declare your datatype — the code will not run.
You can use a special comment # type: ignore[code, ...] to only ignore errors with a specific error code (or codes) on a particular line. This can be used even if you have not configured mypy to show error codes. Currently it's only possible to disable arbitrary error codes on individual lines using this comment.
“Mypy is an optional static type checker for Python that aims to combine the benefits of dynamic (or 'duck') typing and static typing. Mypy combines the expressive power and convenience of Python with a powerful type system and compile-time type checking.” A little background on the Mypy project.
As described in sanyash's answer, os.PathLike
is defined as a generic type. You can look at the stub in the typeshed repo. However, os.PathLike
is only generic in the stub file, the implementation importable from os
is not.
Not specifying the type var parameter (path: PathLike
) leads to a mypy error. Specifying the type var parameter (path: PathLike[Any]
) leads to a Python interpreter (runtime) error.
This exact issue has been raised in the mypy repo as #5667. As a result PR #5833 extends the mypy documentation:
The added section points out three ways to handle this:
Interpretation of the annotation by the Python interpreter (at runtime) can be disabled with a special from __future__ import annotations
import, see here. This is planned to become the default in Python 3.10 and solves a bunch of annotation related issues.
from __future__ import annotations
from os import PathLike
import subprocess
from typing import Any, Sequence, Union
def run(shell_command: Union[bytes, str, Sequence[Union[bytes, str, PathLike[Any]]]], **subprocess_run_kwargs: Any) -> int:
return subprocess.run(shell_command, check=True, shell=True, **subprocess_run_kwargs).returncode
Use typing.TYPE_CHECKING
to supply different annotations depending on whether a type checker or the Python interpreter (runtime) interprets the file.
from os import PathLike
import subprocess
from typing import Any, Sequence, TYPE_CHECKING, Union
if TYPE_CHECKING:
BasePathLike = PathLike[Any]
else:
BasePathLike = PathLike
def run(shell_command: Union[bytes, str, Sequence[Union[bytes, str, BasePathLike]]], **subprocess_run_kwargs: Any) -> int:
return subprocess.run(shell_command, check=True, shell=True, **subprocess_run_kwargs).returncode
Supply the annotation as a string. The Python interpreter (runtime) will not interpret the annotation, but mypy picks up the proper meaning.
import subprocess
from typing import Any, Sequence, Union
def run(shell_command: Union[bytes, str, Sequence[Union[bytes, str, 'PathLike[Any]']]], **subprocess_run_kwargs: Any) -> int:
return subprocess.run(shell_command, check=True, shell=True, **subprocess_run_kwargs).returncode
PathLike
is a generic type, so you need to use it with a type parameter (AnyStr
for example):
import subprocess
from os import PathLike
from typing import Union, Sequence, Any, AnyStr
def run(shell_command: Union[bytes, str, Sequence[Union[bytes, str, PathLike[AnyStr]]]], **subprocess_run_kwargs: Any) -> int:
return subprocess.run(shell_command, check=True, shell=True, **subprocess_run_kwargs).returncode
Related issues:
UPDATE
Sorry, I didn't check this code at runtime. With some tricks it is possible to write a workaround:
import subprocess
from os import PathLike as BasePathLike
from typing import Union, Sequence, Any, AnyStr, TYPE_CHECKING
import abc
if TYPE_CHECKING:
PathLike = BasePathLike
else:
class FakeGenericMeta(abc.ABCMeta):
def __getitem__(self, item):
return self
class PathLike(BasePathLike, metaclass=FakeGenericMeta):
pass
def run(shell_command: Union[bytes, str, Sequence[Union[bytes, str, PathLike[AnyStr]]]], **subprocess_run_kwargs: Any) -> int:
return subprocess.run(shell_command, check=True, shell=True, **subprocess_run_kwargs).returncode
Issues related to this workaround:
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