Python Type Hints - Use object instead of Any

2021-05-07 A type holder.

When starting out with Python type hints, it’s common to overuse typing.Any. This is dangerous, since Any entirely disables type checking for a variable, allowing any operation.

If you’re using Any to mean “this object could be any type, and I don’t care what”, you probably want to use object instead. Every object in Python inherits from object, which makes it an “opaque” type that only allows operations common to everything. Therefore we could pass, print, or store such variables in a container, but we couldn’t do anything more specific.

Simple Example

Take these few lines of code:

from typing import Any

x: Any = 123
x.does_not_exist()

We assign an int to x, which we marked explicitly as having type Any. The next line has an operation that will fail, since ints do not have a does_not_exist() method. But, due to the Any type, mypy does not detect the failure:

$ mypy example.py
Success: no issues found in 1 source file

If we didn’t care what type x contained, we would be better off typing it as object:

x: object = 123
x.does_not_exist()

Mypy can then correctly detect the bug:

$ mypy example.py
example.py:2: error: "object" has no attribute "does_not_exist"  [attr-defined]
Found 1 error in 1 file (checked 1 source file)

Variable Arguments

Another example where Any can be overused is when passing through *args and **kwargs:

from typing import Any

from example import Widget


class BlueWidget(Widget):
    def __init__(self, *args: Any, blueness: int = 50, **kwargs: Any) -> None:
        super().__init__(*args, **kwargs)
        self.blueness = blueness

Since we are only passing on args and kwargs, the use of Any doesn’t really have an effect. But if the code ever evolves to look inside args or kwargs, for example to log them, then such operations will not be type checked.

We should instead use object to declare that we don’t know or care about the types of the values in args and kwargs:

from example import Widget


class BlueWidget(Widget):
    def __init__(self, *args: object, blueness: int = 50, **kwargs: object) -> None:
        super().__init__(*args, **kwargs)
        self.blueness = blueness

Using object is also less work since we don’t have to import it!

Fin

Any questions?

—Adam


Want better tests? Check out my book Speed Up Your Django Tests which teaches you to write faster, more accurate tests.


Subscribe via RSS, Twitter, or email:

One summary email a week, no spam, I pinky promise.

Related posts:

Tags: mypy, python