1

I tested the following snippet with 4 common type checkers for Python and, to my surprise, none of them complained:

from typing import Any

def length(s: str) -> int:
    return len(s)

def any_length(o: Any) -> int:
    return length(o)

if __name__ == "__main__":
    print(any_length(1234))

It's easy to predict that running this code will result in an exception:

TypeError: object of type 'int' has no len()

mypy:

Success: no issues found in 1 source file

pytype:

Success: no errors found

pyright:

0 errors, 0 warnings, 0 informations
Completed in 0.435sec

pyre:

ƛ No type errors found

I would expect at least a warning saying that Any is not a subtype of str and therefore application of length: str -> int to an object of type Any is unsafe. Is there something about these particular types that makes it difficult for type checkers to consider this simple case? The problem of determining whether a concrete type is a subtype of another doesn't seem undecidable, but maybe I'm wrong here?

wjandrea
  • 28,235
  • 9
  • 60
  • 81
shooqie
  • 950
  • 7
  • 17

1 Answers1

6

Any is not just a synonym for object.

Any is somewhat like both a supertype of any type, and a subtype of any type, whereas object is a supertype of all types but only a subtype of itself. (Which is not to say that Any is a sub- or supertype of any type, since Any is not a type.)

Intuitively,

  1. If a function expects a value of type Any, it will accept anything, as if Any were a universal supertype.

  2. But no matter what a function expects, it will accept a value of type Any, as if Any were a universal subtype.

A fuller description of Any can be found in the Summary of gradual typing in PEP 484, "The Theory of Type Hints". But the Any type is basically what allows you to type-check code that doesn't provide hints for everything. If something has no type hint, it is assumed to have type Any, which means there are no static restrictions on how it is used, just as you would expect from a dynamially typed language.


Specifically, as the static type of o is Any, it is a valid argument for the function length which expects a str (as Any is consistent with str). That doesn't mean any_length(1234) will work at runtime, because you basically lied about the type of value any_length requires to pass to length.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • 3
    To put a finer point on it, if you want type checking and you want to support _any_ type (that is actually a type), use `object`, not `Any`. Theoretically, not `def any_length(o: Any) -> int: ...`, but `def any_length(o: object) -> int: ...`. (Practically, though, `def any_length(o: Sized) -> int:` is probably better.) – kojiro Feb 13 '23 at 20:22
  • 1
    Thank you for the PEP link. But I still find it difficult to grasp the (subtle, I imagine), distinction here: `A type t1 is consistent with a type t2 if t1 is a subtype of t2. (But not the other way around.)` but at the same time `Any is consistent with every type. (But Any is not a subtype of every type.)` and `Every type is consistent with Any. (But every type is not a subtype of Any.)` – shooqie Feb 13 '23 at 20:29
  • 2
    I should take that wording out of my answer, since `Any` is not a type itself, only an "extension" of the class of types that allows for the definition of the `is-consistent-with` relation. – chepner Feb 13 '23 at 20:30
  • real and true i agree – Virang Patel Feb 13 '23 at 20:33