My function needs to accept an object, from which data can be extracted by index, viz. a List
or an instance with defined __getitem__
method.
Which type can I use for type hinting this argument?
Update: As I understand there are presently no such type, I tried to make one myself:
class IndexableContainer(Generic[int, ReturnType]):
def __getitem__(self, key: int) -> ReturnType:
...
But I get the following error:
File "indexable_container.py", line 22, in IndexableContainer
class IndexableContainer(Generic[int, ReturnType]):
File ".../lib/python3.6/typing.py", line 682, in inner
return func(*args, **kwds)
File ".../lib/python3.6/typing.py", line 1112, in __getitem__
"Parameters to Generic[...] must all be type variables")
TypeError: Parameters to Generic[...] must all be type variables
How should I do it?
Here's how you can add type hints to our function: Add a colon and a data type after each function parameter. Add an arrow ( -> ) and a data type after the function to specify the return data type.
Type hints help you build and maintain a cleaner architecture. The act of writing type hints forces you to think about the types in your program. While the dynamic nature of Python is one of its great assets, being conscious about relying on duck typing, overloaded methods, or multiple return types is a good thing.
Python will always remain a dynamically typed language. However, PEP 484 introduced type hints, which make it possible to also do static type checking of Python code. Unlike how types work in most other statically typed languages, type hints by themselves don't cause Python to enforce types.
No, type hints don't force Python to check the type of data being stored in a variable. They are just for the developer's reference and help auto-completion software. They allow IDEs to recognise data types and give suggestions based on this information.
This answer to a related question suggests typing.Sequence
.
This type supports both __getitem__
and __len__
.
Given that it is currently deprecated, however, I suppose it would be better to use collections.abc.Sequence
.
As mentioned by the author later on in the comments, however, he/she actually needs something with __delitem__
too, in which case collections.abc.MutableSequence
may be the most appropriate (it is also mentioned by @Yuval in the comments). It supports all of __getitem__
, __setitem__
, __delitem__
, __len__
, and insert
.
An example usage of the final type (adapted from the reference answer):
from collections.abc import MutableSequence
def foo(bar: MutableSequence[Any]):
# bar is a mutable sequence of any objects
There are several different ways you can do this.
If you're ok with using only custom classes (that you can write) as indexable containers, all you need to do is to adapt your code and remove that 'int' type parameter:
class IndexableContainer(Generic[ReturnType]):
def __getitem__(self, key: int) -> ReturnType:
...
class MyCustomContainer(IndexableContainer[ReturnType]):
def __getitem__(self, key: int) -> ReturnType:
# Implementation here
def requires_indexable_container(container: IndexableContainer[ReturnType]) -> ReturnType:
# Code using container here
The issue is, of course, that if you wanted to pass in a plain old list into the function, you wouldn't be able to do so since list doesn't subclass your custom type.
We could maybe special-case certain inputs via clever use of the @overload
decorator and unions, but there's a second, albeit experimental, way of doing this known as Protocols.
Protocols basically let you express "duck typing" in a sane way using type hints: the basic idea is that we can tweak IndexableContainer to become a protocol. Now, any object that implements the __getitem__
method with the appropriate signature is counted as a valid IndexableContainer, whether or not they subclass that type or not.
The only caveat is that Protocols are currently experimental and (afaik) only supported by mypy. The plan is to eventually add protocols to the general Python ecosystem -- see PEP 544 for the specific proposal -- but I haven't kept track of the discussion/don't know what the status of that is.
In any case, to use protocols, install the typing_extensions
module using pip. Then, you can do the following:
from typing_extensions import Protocol
# ...snip...
class IndexableContainer(Protocol, Generic[ReturnType]):
def __getitem__(self, key: int) -> ReturnType:
...
def requires_indexable_container_of_str(container: IndexableContainer[str]) -> None:
print(container[0] + "a")
a = ["a", "b", "c"]
b = {0: "foo", 2: "bar"}
c = "abc"
d = [1, 2, 3]
# Type-checks
requires_indexable_container_of_str(a)
requires_indexable_container_of_str(b)
requires_indexable_container_of_str(c)
# Doesn't type-check
requires_indexable_container_of_str(d)
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