Suppose I've got a map
like function:
def generate(data, per_element):
for element in data:
per_element(element)
How can I add type-hints so that if I call generate(some_data, some_function)
where some_data: List[SomeClass]
, I get a warning if SomeClass
is missing a field used by some_function
?
As an example - with the following code:
def some_function(x):
print(x.value)
some_data: List[int] = [1, 2, 3]
generate(some_data, some_function)
I would like to get a warning that int
does not have the attribute value
.
CodePudding user response:
Use a type variable to make generate
generic in the type of object that data
contains and that per_element
expects as an argument.
from typing import TypeVar, List, Callable
T = TypeVar('T')
def generate(data: List[T], per_element: Callable[[T], Any]):
for element in data:
per_element(element)
class Foo:
def __init__(self):
self.value = 3
def foo(x: Foo):
print(x.value)
def bar(x: int):
pass
generate([Foo(), Foo()], foo) # OK
# Argument 2 to "generate" has incompatible type "Callable[[Foo], Any]"; expected "Callable[[int], Any]"
generate([1,2,3], foo)
Whatever T
is, it has to be the same type for both the list and the function, to ensure that per_element
can, in fact, be called on every value in data
. The error produced by the second call to generate
isn't exactly what you asked for, but it essentially catches the same problem: the list establishes what type T
is bound to, and the function doesn't accept the correct type.
If you specifically want to require that T
be a type whose instances have a value
attribute, it's a bit trickier. It's similar to the use case for Protocol
, but that only supports methods (or class attributes in general?), not instance attributes, as far as I know. Perhaps someone else can provide a better answer.
CodePudding user response:
Seems like you're searching for:
def generate(data: List[AClass], per_element):
for element in data:
per_element(element)
So that AClass implements the method you need.
CodePudding user response:
Your class needs the value
attribute:
class SomeClass:
value: Any # I used any but use whatever type hint is appropriate
Then using typing.Callable
in your function as well as the builtin types. starting with python 3.7 and finally fully implemented in python 3.9 you can use the builtins themselves as well as in python 3.9 you can use parameter specifications
from typing import ParamSpec, TypeVar, Callable
P = ParamSpec("P")
R = TypeVar("R")
def generate(data: list[SomeClass], per_element: Callable[P, R]) -> None:
for element in data:
per_element(element)
Then in some_function
using the class type hint and None return variable:
def some_function(x: SomeClass) -> None:
print(x.value)