Reputation: 1438
I have a function that uses the len
function on one of it's parameters and iterates over the parameter. Now I can choose whether to annotate the type with Iterable
or with Sized
, but both gives errors in mypy
.
from typing import Sized, Iterable
def foo(some_thing: Iterable):
print(len(some_thing))
for part in some_thing:
print(part)
Gives
error: Argument 1 to "len" has incompatible type "Iterable[Any]"; expected "Sized"
While
def foo(some_thing: Sized):
...
Gives
error: Iterable expected
error: "Sized" has no attribute "__iter__"
Since there is no Intersection
as discussed in this issue I need to have some kind of mixed class.
from abc import ABCMeta
from typing import Sized, Iterable
class SizedIterable(Sized, Iterable[str], metaclass=ABCMeta):
pass
def foo(some_thing: SizedIterable):
print(len(some_thing))
for part in some_thing:
print(part)
foo(['a', 'b', 'c'])
This gives an error when using foo
with a list
.
error: Argument 1 to "foo" has incompatible type "List[str]"; expected "SizedIterable"
This is not too surprising since:
>>> SizedIterable.__subclasscheck__(list)
False
So I defined a __subclasshook__
(see docs).
class SizedIterable(Sized, Iterable[str], metaclass=ABCMeta):
@classmethod
def __subclasshook__(cls, subclass):
return Sized.__subclasscheck__(subclass) and Iterable.__subclasscheck__(subclass)
Then the subclass check works:
>>> SizedIterable.__subclasscheck__(list)
True
But mypy
still complains about my list
.
error: Argument 1 to "foo" has incompatible type "List[str]"; expected "SizedIterable"
How can I use type hints when using both the len
function and iterate over my parameter? I think casting foo(cast(SizedIterable, ['a', 'b', 'c']))
is not a good solution.
Upvotes: 85
Views: 33009
Reputation: 331
Maybe you need a class like this?
class MyArray:
_array: list
def as_tuple(self):
return tuple(self._array)
def as_list(self):
return self._array
# More Feature to implement
Upvotes: 0
Reputation: 1112
You should go with Sequence
from typing if you plan to use only list or tuple and access its elements by index, like x[0]
. Sequence
is both Sized
and Iterable
, see here.
Upvotes: 22
Reputation: 3703
Starting from Python3.6 there's a new type called Collection
. See here.
Upvotes: 68
Reputation: 1438
In the future Protocol
s will be introduced. They are already available through typing_extensions
. See also PEP 544. Using Protocol
the code above would be:
from typing_extensions import Protocol
class SizedIterable(Protocol):
def __len__(self):
pass
def __iter__(self):
pass
def foo(some_thing: SizedIterable):
print(len(some_thing))
for part in some_thing:
print(part)
foo(['a', 'b', 'c'])
mypy
takes that code without complaining. But PyCharm is saying
Expected type 'SizedIterable', got 'List[str]'
about the last line.
Upvotes: 16