There are times when you need to treat a function or method as a variable or argument (Callable object) in Python.
I will write an article about the type annotation in such a case as a memorandum.
Suppose you have a function like the one below, which accepts a function object (func
) and an arbitrary value (x
) as arguments.
def any_function(func, x):
return func(x)
Obviously, if you do this, even if you mouse over VS Code, the information such as the type will be unknown. Type check is not performed even with Pylance (Pyright). There is also a countermeasure to write the details in detail with docstring etc., but if it is left as it is, it is ambiguous and it is easy to make mistakes.
Next, add the Callable type annotation to the func argument. Callable is in the built-in typing package. In addition, type annotation is also added to the x argument.
from typing import Callable
def any_function(func: Callable, x: int) -> int:
return func(x)
This will recognize that the func argument is some kind of function or method that you can call when you hover over VS Code.
Also, if you specify something other than a function in the func argument, it will be caught in the Pylance check.
any_function(func=10, x=20)
However, the information of the argument and the return value when the mouse is over remains Unknown.
Type annotations such as returning the func function as an int as the first argument, float as the second argument, and float as the return value (add the flaot argument y
to the sample argument as well). ..
As a way of writing, write as Callable [[type of first argument, type of second argument], type of return value]
. When the argument increases or decreases, it is increased or decreased separated by commas.
def any_function(
func: Callable[[int, float], float],
x: int,
y: float) -> float:
return func(x, y)
As a result, the type information of the function argument and return value is displayed when the mouse is over, and when the func argument is called, if the argument is insufficient or the type does not match, it can be detected as an error.
Sample with insufficient arguments at func call location
def any_function(
func: Callable[[int, float], float],
x: int,
y: float) -> float:
return func(x)
Samples with mismatched types in func call
def any_function(
func: Callable[[int, float], float],
x: int,
y: float) -> float:
return func(y, x)
When passing it as an argument, it is checked whether the structure of the specified function matches.
Sample that the return value of Callable passed to the argument does not match and is caught in the check
def any_function(
func: Callable[[int, float], float],
x: int,
y: float) -> float:
return func(x, y)
def other_function(x: int, y: float) -> str:
...
any_function(func=other_function, x=10, y=12.5)
Even with the support so far, it has become much more robust than the case without type annotation.
However, in this case, the original argument name cannot be used when the keyword argument is specified. The argument names are not preserved and are assigned the names p0, p1, .... For example, if you write with keyword arguments as shown below, it will be caught in the Pylance check.
def any_function(
func: Callable[[int, float], float],
x: int,
y: float) -> float:
return func(x=x, y=y)
In many cases this is not a problem, but for example, if you have many arguments or many arguments of the same type, you will want to use keyword arguments.
In fact, you can use the typing package Protocol to perform type annotation while preserving argument names (I recently learned).
To use it, define a class that inherits Protocol, and define the arguments, return value, and type information required for the __call__
method (this time, I named it FuncType
as a sample). All you have to do is specify the class (FuncType
) in the type annotation of the func argument.
from typing import Callable, Protocol
class FuncType(Protocol):
def __call__(self, x: int, y: float) -> float:
...
def any_function(
func: FuncType,
x: int,
y: float) -> float:
return func(x=x, y=y)
Now, even if you use keyword arguments, you will not get an error on Pylance.
Protocol is available in Python 3.8 and above. If you want to use it with Python 3.7 or earlier, or if you want to support past Python versions with distribution libraries etc., you need to install typing-extensions library with pip etc. as a backport. If you install mypy etc., it will be installed together.
The description of import also changes to typing_extensions instead of typing module. The behavior and usage are the same for both the typing Protocol and the typing_extensions Protocol.
from typing_extensions import Protocol
Recommended Posts