[Python-Dev] What's missing in PEP-484 (Type hints)

Steven D'Aprano steve at pearwood.info
Thu Apr 30 14:33:46 CEST 2015


On Thu, Apr 30, 2015 at 01:41:53PM +0200, Dima Tisnek wrote:

> # Syntactic sugar
> "Beautiful is better than ugly, " thus nice syntax is needed.
> Current syntax is very mechanical.
> Syntactic sugar is needed on top of current PEP.

I think the annotation syntax is beautiful. It reminds me of Pascal.


> # internal vs external
> @intify
> def foo() -> int:
>     b = "42"
>     return b  # check 1
> x = foo() // 2  # check 2
> 
> Does the return type apply to implementation (str) or decorated callable (int)?

I would expect that a static type checker would look at foo, and flag 
this as an error. The annotation says that foo returns an int, but it 
clearly returns a string. That's an obvious error.

Here is how I would write that:


# Perhaps typing should have a Function type?
def intify(func: Callable[[], str]) -> Callable[[], int]:
    @functools.wraps(func)
    def inner() -> int:
        return int(func())
    return inner


@intify
def foo() -> str:
    b = "42"
    return b


That should, I hope, pass the type check, and without lying about the 
signature of *undecorated* foo.

The one problem with this is that naive readers will assume that 
*decorated* foo also has a return type of str, and be confused. That's a 
problem. One solution might be, "don't write decorators that change the 
return type", but that seems horribly restrictive. Another solution 
might be to write a comment:

@intify  # changes return type to int
def foo() -> str:
    ...

but that's duplicating information already in the intify decorator, and 
it relies on the programmer writing a comment, which people don't do 
unless they really need to.

I think that the only solution is education: given a decorator, you 
cannot assume that the annotations still apply unless you know what the 
decorator does.


> How can same annotation or a pair of annotations be used to:
> * validate return statement type
> * validate subsequent use
> * look reasonable in the source code
> 
> 
> # lambda
> Not mentioned in the PEP, omitted for convenience or is there a rationale?
> f = lambda x: None if x is None else str(x ** 2)
> Current syntax seems to preclude annotation of `x` due to colon.
> Current syntax sort of allows lamba return type annotation, but it's
> easy to confuse with `f`.

I don't believe that you can annotate lambda functions with current 
syntax. For many purposes, I do not think that is important: a good type 
checker will often be able to infer the return type of the lambda, and 
from that infer what argument types are permitted:

    lambda arg: arg + 1

Obviously arg must be a Number, since it has to support addition with 
ints.


> # local variables
> Not mentioned in the PEP
> Non-trivial code could really use these.

Normally local variables will have their type inferred from the 
operations done to them:

    s = arg[1:]  # s has the same type as arg

When that is not satisfactory, you can annotate variables with a comment:

    s = arg[1:]  #type: List[int]

https://www.python.org/dev/peps/pep-0484/#id24


> # global variables
> Not mentioned in the PEP
> Module-level globals are part of API, annotation is welcome.
> What is the syntax?

As above.


> # comprehensions
> [3 * x.data for x in foo if "bar" in x.type]
> Arguable, perhaps annotation is only needed on `foo` here, but then
> how complex comprehensions, e.g. below, the intermediate comprehension
> could use an annotation
> [xx for y in [...] if ...]

A list comprehension is obviously of type List. If you need to give a 
more specific hint:

result = [expr for x in things if cond(x)]  #type: List[Whatever]

See also the discussion of "cast" in the PEP.

https://www.python.org/dev/peps/pep-0484/#id25


> # class attributes
> s = socket.socket(...)
> s.type, s.family, s.proto  # int
> s.fileno  # callable
> If annotations are only available for methods, it will lead to
> Java-style explicit getters and setters.
> Python language and data model prefers properties instead, thus
> annotations are needed on attributes.

class Thing:
    a = 42  # can be inferred
    b = []  # inferred as List[Any]
    c = []  #type: List[float]



-- 
Steve


More information about the Python-Dev mailing list