The "Any" type
We've come really far in our typing journey, learning how to annotate, test and
fix the types by using our friendly tool mypy
. And truth be told, I really
enjoy using mypy to figure out type inconsistencies in code, and making it more
robust.
But there's a small problem with this: sometimes you don't want to define a strict type for a part of your code. Some other times, you actually can't define an exact type. This all stems from the fact that Python is an extremely dynamic language. You can read arbitrary data from files and convert it into objects at runtime. You can even do crazy things like defining new classes while the code is running!
And because of how dynamic Python is, it is impossible to statically type some kinds of Python code. It's what makes Python a powerful language in the first place.
A common example of such a pattern is duck typing:
def do_quack(duck):
if hasattr(duck, "quack"):
quack_function = duck.quack
quack_function()
else:
print("Expected an object with a 'quack' method.")
class Goose:
def quack(self):
print("Goose goes 'quack'.")
goose = Goose()
do_quack(goose)
The fact that we can look up if the passed object has a quack
method on it,
allows us to pass any kind of object that "can quack", be it a Duck
, or a
Goose
, or something else entirely.
For this code, even if we were able to add types to the code, mypy simply can't
figure out the type of quack_function
, because duck.quack
can be anything
depending on the object passed.
For this reason, usually what you do is tell mypy to just ignore checking this
object when type checking. You do this by using the Any
type from the typing
module:
from typing import Any
def do_quack(duck: Any) -> None:
if hasattr(duck, "quack"):
quack_function = duck.quack
quack_function()
else:
print("Expected an object with a 'quack' method.")
class Goose:
def quack(self) -> None:
print("Goose goes 'quack'.")
goose = Goose()
do_quack(goose)
Doing this will essentially let you do anything with the duck
object, even
things that will crash at runtime. Any
tells mypy to leave testing this code
for the developers. Whenever possible, you should try to avoid using Any
.
We'll be looking into duck typing at a much deeper level, and mitigating the use
of Any
in such cases in the next chapter.
Another common example is when you don't know the exact type of something. Let's say you have to build a library around an API, that returns some data for your users. The API is currently in development, and the data returned by the API can change at any time:
data = {
'name': 'Mr. User',
'age': 33,
'identity_proofs': ['Passport', 'Birth Certificate'],
'is_employed': True,
}
The data object currently contains things like strings, ints and lists, but it
can change in the future at any time, and you want to avoid spending time
defining exact types for this data, until it is finalized. You can also use
Any
for this case:
from typing import Any
def pretty_print_data(user: Any) -> None:
print(f"User {user['name']} is {user['age']} years old.")
print(f"They have provided {user['identity_proofs']} as their IDs.")
data = {
'name': 'Mr. User',
'age': 33,
'identity_proofs': ['Passport', 'Birth Certificate'],
'is_employed': True,
}
pretty_print_data(data)
This way, you can use mypy to validate the rest of your code, and come back to this part of the code later.