107

Is there any difference between using typing.Any as opposed to object in typing? For example:

def get_item(L: list, i: int) -> typing.Any:
    return L[i]

Compared to:

def get_item(L: list, i: int) -> object:
    return L[i]
Alex Waygood
  • 6,304
  • 3
  • 24
  • 46
Markus Meskanen
  • 19,939
  • 18
  • 80
  • 119

2 Answers2

110

Yes, there is a difference. Although in Python 3, all objects are instances of object, including object itself, only Any documents that the return value should be disregarded by the typechecker.

The Any type docstring states that object is a subclass of Any and vice-versa:

>>> import typing
>>> print(typing.Any.__doc__)
Special type indicating an unconstrained type.

    - Any object is an instance of Any.
    - Any class is a subclass of Any.
    - As a special case, Any and object are subclasses of each other.

However, a proper typechecker (one that goes beyond isinstance() checks, and which inspects how the object is actually used in the function) can readily object to object where Any is always accepted.

From the Any type documentation:

Notice that no typechecking is performed when assigning a value of type Any to a more precise type.

and

Contrast the behavior of Any with the behavior of object. Similar to Any, every type is a subtype of object. However, unlike Any, the reverse is not true: object is not a subtype of every other type.

That means when the type of a value is object, a type checker will reject almost all operations on it, and assigning it to a variable (or using it as a return value) of a more specialized type is a type error.

and from the mypy documentation section 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.

object can be cast to a more specific type, while Any really means anything goes and a type checker disengages from any use of the object (even if you later assign such an object to a name that is typechecked).

You already painted your function into a an un-typed corner by accepting list, which comes down to being the same thing as List[Any]. The typechecker disengaged there and the return value no longer matters, but since your function accepts a list containing Any objects, the proper return value would be Any here.

To properly participate in type-checked code, you need to mark your input as List[T] (a genericly typed container) for a typechecker to then be able to care about the return value. Which in your case would be T since you are retrieving a value from the list. Create T from a TypeVar:

from typing import TypeVar, List

T = TypeVar('T')

def get_item(L: List[T], i: int) -> T:
    return L[i]
Michael0x2a
  • 58,192
  • 30
  • 175
  • 224
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • That's interesting, one more special case to the `object`/`type` structure. Thanks again Martijn! :) – Markus Meskanen Oct 02 '16 at 12:45
  • Unfortunately, this isn't quite correct -- see my answer below. In particular, the key thing is that `Any` is meant to be fully unconstrained -- any operation is permitted on a value of type `Any`. In contrast, object is the most constrained type. If you have a value of type `Any`, the only operations you're allowed to do are ones that are a part of the `object` interface (things like `__str__` and such). The "object is a subclass of Any and vice versa" exists mainly to explain why all values are compatible with `Any` even though they aren't technically a subclass or superclass of that type. – Michael0x2a Oct 02 '16 at 20:46
  • @Michael0x2a: right, so from a typing point of view `Any` 'allows' the function to use `object.__missing__`, while `object` would not as that method is optional. In that way `Any` documents how the function will use the argument rather than just the dryly apply the isinstance test. In practice, the two remain the same because the `isinstance()` test used in `typing` will pass either way. – Martijn Pieters Oct 03 '16 at 06:38
  • @MartijnPieters: I'm not sure if I fully agree. From experience, using `Any` over `object` ends up having major ramifications due to how it weakens the typesafety of your code. E.g. It would be legal to pass a value of type `Any` into a function expecting some `CustomType` even if doing so would ultimately cause a runtime error because literally any operation is allowable with `Any`. The same isn't true for `object`. I also think `object` captures the intent more precisely since it outlines exactly what interface is safe to use on the value rather then leaving it ambiguous. – Michael0x2a Oct 03 '16 at 07:40
  • @Michael0x2a: how would accepting `Any` in that case differ from accepting `object`? I would consider *either* a bug if the actual expectation differs because some kind of operation is executed on that object that isn't supported. Passing a plain `object()` instance suffices for either declaration and would cause a runtime error. Note that I still advocate using `Any` here. – Martijn Pieters Oct 03 '16 at 08:21
  • 1
    @Michael0x2a: Ah, I see what you mean, updated my answer to reflect this. I was purely looking at the `typing` implementation here. – Martijn Pieters Oct 03 '16 at 08:27
  • @MartijnPieters -- cool, I agree with your answer now :). As an aside, the `typing` implementation was deliberately designed to have minimal runtime behavior as possible (and I think Guido mentioned some slight regret at not making the implementation even simpler then it is now when I asked him once. [Subclass checks](https://github.com/python/typing/pull/283) were recently killed/neutered, for example). As a result it's often hard to deduce how types interact by just looking at the implementation of the typing module itself -- PEP 484 is usually a more useful resource. – Michael0x2a Oct 03 '16 at 08:59
  • @Michael0x2a: yup, which is why the PEP is [still provisional](https://docs.python.org/3/glossary.html#term-provisional-api). :-) – Martijn Pieters Oct 03 '16 at 09:09
29

Any and object are superficially similar, but in fact are entirely opposite in meaning.

object is the root of Python's metaclass hierarchy. Every single class inherits from object. That means that object is in a certain sense the most restrictive type you can give values. If you have a value of type object, the only methods you are permitted to call are ones that are a part of every single object. For example:

foo = 3  # type: object

# Error, not all objects have a method 'hello'
bar = foo.hello()   

# OK, all objects have a __str__ method
print(str(foo))   

In contrast, Any is an escape hatch meant to allow you to mix together dynamic and statically typed code. Any is the least restrictive type -- any possible method or operation is permitted on a value of type Any. For example:

from typing import Any
foo = 3  # type: Any

# OK, foo could be any type, and that type might have a 'hello' method
# Since we have no idea what hello() is, `bar` will also have a type of Any
bar = foo.hello()

# Ok, for similar reasons
print(str(foo))

You should generally try and use Any only for cases where...

  1. As a way of mixing together dynamic and statically typed code. For example, if you have many dynamic and complex functions, and don't have time to fully statically type all of them, you could settle for just giving them a return type of Any to nominally bring them into the typechecked work. (Or to put it another way, Any is a useful tool for helping migrate an untypechecked codebase to a typed codebase in stages).
  2. As a way of giving a type to an expression that is difficult to type. For example, Python's type annotations currently do not support recursive types, which makes typing things like arbitrary JSON dicts difficult. As a temporary measure, you might want to give your JSON dicts a type of Dict[str, Any], which is a bit better then nothing.

In contrast, use object for cases where you want to indicate in a typesafe way that a value MUST literally work with any possible object in existence.

My recommendation is to avoid using Any except in cases where there is no alternative. Any is a concession -- a mechanism for allowing dynamism where we'd really rather live in a typesafe world.

For more information, see:


For your particular example, I would use TypeVars, rather then either object or Any. What you want to do is to indicate that you want to return the type of whatever is contained within the list. If the list will always contain the same type (which is typically the case), you would want to do:

from typing import List, TypeVar

T = TypeVar('T')
def get_item(L: List[T], i: int) -> T:
    return L[i]

This way, your get_item function will return the most precise type as possible.

Markus Meskanen
  • 19,939
  • 18
  • 80
  • 119
Michael0x2a
  • 58,192
  • 30
  • 175
  • 224
  • Wouldn't `List[T]` constrict the list to be homogenous, e.g. `[None, 1, 'foo']` is illegal as there is no *one* type in that list? By using `list` as the accepted type, the baby has already been chucked out with the bathwater; no constraint is set on the contained values. – Martijn Pieters Oct 03 '16 at 08:22
  • 1
    @MartijnPieters -- not necessarily -- `T` could be constrained to be either `Any` or `Union[None, int, str]`. Both alternatives would make your example of `[None, 1, 'foo']` typecheck. But yeah, setting the type to `list` is equivalent to doing `List[Any]`, as you said. – Michael0x2a Oct 03 '16 at 08:30
  • Right, a declaration *elsewhere* where the list originates should properly define the list contents. I do agree it is much better to actually restrict here; `Any` kills off the typechecker for any subsequent use of the return value of `get_item()` (you can assign the return value to a variable previously constrained and it'll Just Work, to your peril). – Martijn Pieters Oct 03 '16 at 08:40