shooqie
shooqie

Reputation: 1022

Popular Python type checkers give a false negative with Any annotation

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?

Upvotes: 1

Views: 103

Answers (1)

chepner
chepner

Reputation: 530843

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.

Upvotes: 6

Related Questions