Typehints have been introduced in
Python since Python 3.5. As part of this introduction, a new module
introduced which contained various types for built-in collections and Abstract
Iterable is a type that is prominantly used in the PEP for Type hints and is implemented by many of the most used Python collections. Since Python 3.9, this type can be imported from
collections.abc and can be used as a type as well as an Abstract Base Class.
If you enthousiastically started using type hints in Python and have been following best practices to accept the most generic type possible in your arguments, you might have been using
Iterable can be used at any place where your code expects the variable to implement
__iter__, in other words, where you code wants to iterate through a collection of some sorts.
Say you want to send an email to multiple receivers:
class MailApi: def sendmail(self, sender: str, receiver: str, message: str) -> None: ... api = MailApi() def send_emails(message: str, sender: str, receivers: Iterable[str]) -> Optional[T]: if not receivers: raise ValueError("you need atleast one receiver") for receiver in receivers: api.sendmail(sender, receiver, message)
If you send an email using the following code you will send an email:
send_emails( "Hello", "email@example.com", ["firstname.lastname@example.org", "email@example.com", "firstname.lastname@example.org"], )
If you use the following code, you will get an exception:
send_emails("Hello", "email@example.com", )
But what if you did this?
send_emails( "Hello", "firstname.lastname@example.org", filter( lambda x: not x.endswith("example.com"), ["email@example.com", "firstname.lastname@example.org", "email@example.com"], ), )
You would intuitively expect an exception, because the filter will remove all matching emails, but Nothing will happen!
filter returns a valid
Iterable and MyPy correctly asserts that there are no type errors. What is going on?
The pythonic way, to check if a collection is empty, is to use
if not <collection_name. This works because Python returns
Iterable is not required to implement
__len__ as well as
__bool__. The default truth value is
True, so even though your code is perfectly Type safe, it will not do what you might expect. A solution to this problem is not simple, but you could prevent similar bugs by using a slightly less generic type such as
Collection, which does require
__len__ to be implemented, if you want to catch these errors with a type checker.
The new Python type hints are a welcome addition to the language and boost maintainability, reduce bugs and allow better editor support for Python. However, due to historic design decsicions there are subtle cases which can result in bugs in ways you wouldn't expect from type safe code. If you use
Iterable in Python, consider using