I'm trying to type annotate a function in Python 2 according to PEP 484. The function accepts a container which should implement both __len__
and __iter__
. The original code where I want to add this annotation is quite complex, so consider an example function which returns the product of all int
s in a container s
if len(s)
is even and returns 1 otherwise.
If I wanted to annotate a container where only __len__
is needed, I would have annotated it as type: (Sized) -> int
. If I wanted to annotate a container where only __iter__
is needed, I would have annotated it as type: (Iterable[int]) -> int
. But how do I perfectly annotate a container where I need both?
I tried this as per Piotr-Ćwiek's suggestion:
from __future__ import print_function
from typing import Sized, Iterable
class SizedIterable(Sized, Iterable[int]):
pass
def product2(numbers):
# type: (SizedIterable) -> int
if len(numbers)%2 == 1:
return 1
else:
p = 1
for n in numbers:
p*= n
return p
print(product2([1, 2, 3, 4]))
print(product2({1, 2, 3, 4}))
but this failed with this error:
prod2.py:17: error: Argument 1 to "product2" has incompatible type List[int]; expected "SizedIterable"
prod2.py:18: error: Argument 1 to "product2" has incompatible type Set[int]; expected "SizedIterable"
Annotations were introduced in Python 3.0 originally without any specific purpose. They were simply a way to associate arbitrary expressions to function arguments and return values. Years later, PEP 484 defined how to add type hints to your Python code, based off work that Jukka Lehtosalo had done on his Ph. D.
To annotate return value type, add -> immediately after closing the parameter parentheses, just before the function definition colon( : ): def announcement(language: str, version: float) -> str: ... The function now has type hints showing that it receives str and float arguments, and returns str .
What are Function annotations? Function annotations are arbitrary python expressions that are associated with various part of functions. These expressions are evaluated at compile time and have no life in python's runtime environment. Python does not attach any meaning to these annotations.
What Are Type Annotations? 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.
In python 3.6, there's typing.Collection
that works almost perfectly for your use case (it also derives from Container
, but realistically anything that you want to use will likely have __contains__
). Unfortunately, there's no solution for python 2.
The reason SizedIterable
doesn't work is that by deriving it from Sized
and Iterable
, you are only telling mypy that it's a subtype of those two types; mypy does not conclude that any type that's a subtype of Sized
and Iterable
is also a subtype of SizedIterable
.
Mypy's is perfectly logical; after all, you wouldn't want this code to type check:
class A(Sized, Iterable[int]):
def g(self) -> None:
...
def f(x: A) -> None:
a.g()
# passes type check because [1, 2] is Sized and Iterable
# but fails in run-time
f([1, 2])
It would be too tricky if mypy treated your class definition differently just because its class body is empty.
In order for mypy to understand your intent, mypy would need to add a new feature to its type system.
Two options for such a feature are being considered at the moment:
Iterable
and Sized
numbers
argument to have __len__
and __iter__
methodsMimicking typing
class definitions or using __instancecheck__
won't work because (as someone explained to me just recently) under no condition will mypy
ever run the code you wrote (i.e., it will never import your module, never call your function, etc.). This is because mypy is a static analysis tool that doesn't assume that the environment to run your code is even available during its execution (e.g., python version, libraries, etc.).
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