Python Sanity Proposal: Type Hinting Solution

Steven D'Aprano steve+comp.lang.python at pearwood.info
Sat Jan 24 00:29:17 EST 2015


Marko Rauhamaa wrote:

> I seem to remember an idea floated on the Scheme mailing list of using
> assertions for such a purpose:
> 
>    def myfunction(arg1, arg2):
>        assert isinstance(arg1, str) and isinstance(arg2, int)
>        return True
> 
> The advantage is that the assertions can be as complex as you'd like:
> 
>    def weekday(day):
>        assert isinstance(day, int) and 0 <= day <= 6
>        ...
> 
>    def str_product(x, y):
>        assert isinstance(x, int) and isinstance(y, str) or \
>            isinstance(x, str) and isinstance(y, int)
>        ...
> 
> Also, they have always been present in the language and assertion
> semantics is fully compatible with static analysis.

Not really. Well, I suppose technically they could be, if the type-checker
included a full Python interpreter.

Requiring the type-checker to parse and understand arbitrarily complex
assertions would require the type-checker to be as complex as Python
itself:


def f(something):
    assert (__import__("somemodule") is 
            some_function(spam, eggs, *ham, **cheese).attr.x[y](z) or
            any(some_expression and another_expression
                for a, b, c, d in my_module.map(something, fe, fi, fo, fum)
                if some_condition(a) or b == c[d]) and
            are_you_confused_yet() if foo else bar or eval(expr, ns) < 23
            or open('config').read(100)[23:27] == 'okay')


Fortunately, type systems are generally not fully Turing complete and are
usually significantly more restricted. PEP 484 doesn't mandate an upper
limit to how clever the type-checker must be, it only sets a common syntax
which type-checkers are expected to support. That is significant less
complex than a full Python parser, and should be lightweight enough that
IDEs and editors can use type-hints for providing text completion and
hints.

(This means that type-checkers are permitted to parse assertions, but they
aren't required to.)

Assertions also have the problem that they execute arbitrarily complex code
at runtime. Type annotations also execute code at runtime, but they're not
expected to be arbitrarily complex: mostly name lookups.

Lastly, this use of assertions clashes with "best practice" for assertions.
Since assertions may be disabled, you should not use them for testing
user-supplied arguments. So that means you have to write:

def func(arg):
    assert isinstance(arg, int)  # satisfy the type checker
    if isinstance(arg, int):  # support times when assert is disabled
        ...


which is not only ugly, but every call requires *two* isinstance checks. It
also means that when asserts are enabled you get a different exception for
bad data than when they are disabled.

This doesn't apply to annotations:

def func(arg:int):
    # since this is a public function, not private, we cannot assume the
    # caller will run the type-checker
    if isinstance(arg, int):
        ...

In this case, the overhead from the "arg:int" annotation is trivial: a
single name lookup and binding when the function is created, not when it is
called.

Sufficiently clever type-checkers may use assertions to infer types, but
requiring this is a non-starter.



-- 
Steven




More information about the Python-list mailing list