.. _dynamic-typing:

Dynamically typed code
======================

In :ref:`getting-started-dynamic-vs-static`, 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).

.. code-block:: python

   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 :ref:`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:

.. code-block:: python

    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``:

.. code-block:: python

    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``:

.. code-block:: python

    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
:option:`--disallow-untyped-defs <mypy --disallow-untyped-defs>` flag.

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

.. code-block:: python

    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 missing generic parameters using the
:option:`--disallow-any-generics <mypy --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 :option:`--ignore-missing-imports <mypy --ignore-missing-imports>`
flag. See :ref:`fix-missing-imports` for more information about this.

.. _any-vs-object:

Any vs. object
--------------

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

.. code-block:: python

    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:

.. code-block:: python

    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 :py:class:`object` or ``Any``, use
:py:class:`object` -- only switch to using ``Any`` if you get a type checker
complaint.

You can use different :ref:`type narrowing <type-narrowing>`
techniques to narrow :py:class:`object` to a more specific
type (subtype) such as ``int``. Type narrowing is not needed with
dynamically typed values (values with type ``Any``).
