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 ints 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