Dynamically typed code

In Dynamic vs static typing, we discussed how bodies of functions that don’t have any explicit type annotations in their function are “dynamically typed” and that mypy will not check them. In this section, we’ll talk a little bit more about what that means and how you can enable dynamic typing on a more fine grained basis.

In cases where your code is too magical for mypy to understand, you can make a variable or parameter dynamically typed by explicitly giving it the type Any. Mypy will let you do basically anything with a value of type Any, including assigning a value of type Any to a variable of any type (or vice versa).

from typing import Any

num = 1         # Statically typed (inferred to be int)
num = 'x'       # error: Incompatible types in assignment (expression has type "str", variable has type "int")

dyn: Any = 1    # Dynamically typed (type Any)
dyn = 'x'       # OK

num = dyn       # No error, mypy will let you assign a value of type Any to any variable
num += 1        # Oops, mypy still thinks num is an int

You can think of Any as a way to locally disable type checking. See Silencing type errors for other ways you can shut up the type checker.

Operations on Any values

You can do anything using a value with type Any, and the type checker will not complain:

def f(x: Any) -> int:
    # All of these are valid!
    x.foobar(1, y=2)
    print(x[3] + 'f')
    if x:
        x.z = x(2)
    open(x).read()
    return x

Values derived from an Any value also usually have the type Any implicitly, as mypy can’t infer a more precise result type. For example, if you get the attribute of an Any value or call a Any value the result is Any:

def f(x: Any) -> None:
    y = x.foo()
    reveal_type(y)  # Revealed type is "Any"
    z = y.bar("mypy will let you do anything to y")
    reveal_type(z)  # Revealed type is "Any"

Any types may propagate through your program, making type checking less effective, unless you are careful.

Function parameters without annotations are also implicitly Any:

def f(x) -> None:
    reveal_type(x)  # Revealed type is "Any"
    x.can.do["anything", x]("wants", 2)

You can make mypy warn you about untyped function parameters using the --disallow-untyped-defs flag.

Generic types missing type parameters will have those parameters implicitly treated as Any:

def f(x: list) -> None:
    reveal_type(x)        # Revealed type is "builtins.list[Any]"
    reveal_type(x[0])     # Revealed type is "Any"
    x[0].anything_goes()  # OK

You can make mypy warn you about untyped function parameters using the --disallow-any-generics flag.

Finally, another major source of Any types leaking into your program is from third party libraries that mypy does not know about. This is particularly the case when using the --ignore-missing-imports flag. See Missing imports for more information about this.

Any vs. object

The type object is another type that can have an instance of arbitrary type as a value. Unlike Any, object is an ordinary static type (it is similar to Object in Java), and only operations valid for all types are accepted for object values. These are all valid:

def f(o: object) -> None:
    if o:
        print(o)
    print(isinstance(o, int))
    o = 2
    o = 'foo'

These are, however, flagged as errors, since not all objects support these operations:

def f(o: object) -> None:
    o.foo()       # Error!
    o + 2         # Error!
    open(o)       # Error!
    n: int = 1
    n = o         # Error!

If you’re not sure whether you need to use object or Any, use object – only switch to using Any if you get a type checker complaint.

You can use different type narrowing techniques to narrow object to a more specific type (subtype) such as int. Type narrowing is not needed with dynamically typed values (values with type Any).