Misusing Any silently switches type checking off; knowing the alternatives keeps your hints meaningful.
Python typing is gradual: typed and untyped code mix, and Any is the bridge.
Any is compatible with every type in both directions. You can pass it anywhere and do anything with it; the checker stops checking. Unannotated parameters are implicitly Any. It's useful while migrating code or for truly dynamic values, but every Any is a hole that hides errors, and it spreads: x.foo on an Any is also Any.
object is the opposite: every value is an object, so anything can be passed in, but you may only do what all objects support (str(x), ==, hash) until you narrow it with isinstance. Use object for "accepts anything" parameters, like a logging helper.
typing.cast(T, value) tells the checker "trust me, this is T". It does nothing at runtime, so a wrong cast is a lie that won't be caught. Prefer narrowing with isinstance when possible.
# type: ignore[code] silences one error on one line; include the error code so unrelated errors on the line still show. mypy's --warn-unused-ignores finds ignores that are no longer needed.
Example
from typing import Any, cast
import json
def log_value(value: object) -> str:
# value.upper() here -> checker error: "object" has no attribute "upper"
if isinstance(value, str):
return value.upper() # fine after narrowing
return repr(value)
raw: Any = json.loads('{"port": 8080}')
port = raw["port"] + 1 # Any: anything goes, nothing checked
config = cast(dict[str, int], raw) # tell the checker; no runtime effect
print(log_value("hi"), log_value(42), port, config["port"]) # HI 42 8081 8080
print(cast(int, "not an int")) # not an int (cast never checks)Remember
- Any turns checking off and spreads; use it sparingly
- object accepts anything but requires narrowing before use
- cast() only informs the checker; it never converts or checks
Watch out for
- Annotating parameters as Any when object (or a Protocol) would keep checking on.