[Python-ideas] Proposal to extend PEP 484 (gradual typing) to support Python 2.7

Guido van Rossum guido at python.org
Wed Jan 20 20:36:58 EST 2016


On Wed, Jan 20, 2016 at 4:54 PM, Andrew Barnert <abarnert at yahoo.com> wrote:

> On Wednesday, January 20, 2016 4:11 PM, Guido van Rossum <guido at python.org>
> wrote:
>
> >On Wed, Jan 20, 2016 at 9:42 AM, Andrew Barnert via Python-ideas <
> python-ideas at python.org> wrote:
> >
> >On Jan 20, 2016, at 06:27, Agustín Herranz Cecilia <
> agustin.herranz at gmail.com> wrote:
> >>>
> >>> - GVR proposal includes some kind of syntactic sugar for function type
> comments (" # type: (t_arg1, t_arg2) -> t_ret "). I think it's good but
> this must be an alternative over typing module syntax (PEP484), not the
> preferred way (for people get used to typehints). Is this syntactic sugar
> compatible with generators? The type analyzers could be differentiate
> between a Callable and a Generator?
> >>
> >>I'm pretty sure Generator is not the type of a generator function, bit
> of a generator object. So to type a generator function, you just write
> `(int, int) -> Generator[int]`. Or, the long way, `Function[[int, int],
> Generator[int]]`.
> >
>
> >There is no 'Function' -- it existed in mypy before PEP 484 but was
> replaced by 'Callable'. And you don't annotate a function def with '->
> Callable' (unless it returns another function).
>
> Sorry about getting the `Function` from the initial proposal instead of
> the current PEP.
>
> Anyway, I don't think the OP was suggesting that. If I interpreted his
> question right:
>
> He was expecting that the comment `(int, int) -> int` was a way to
> annotate a function so it comes out as type `Callable[[int, int], int]`,
> which is correct.


Not really. I understand that you're saying that after:

def foo(a, b):
    # type: (int, int) -> str
    return str(a+b)

the type of 'foo' is 'Callable[[int, int], int]'.

But it really isn't. The type checker (e.g. mypy) knows more at this point:
it knows that foo has arguments named 'a' and 'b' and that e.g. calls like
'foo(1, b=2)' are valid. There's no way to express that using Callable.
Also Callable doesn't support argument defaults.


> And he wanted to know how to instead write a comment for a generator
> function of type `GeneratorFunction[[int, int], int]`, and the answer is
> that you don't. There is no type needed for generator functions; they're
> just functions that return generators.
>

Aha. No wonder I didn't get the question. :-(


> You're right that he doesn't need to know the actual type; you're never
> going to write that, you're just going to annotate the arguments and return
> value, or use the 2.x comment style:
>
>     def f(arg1: int, arg2: int) -> Iterator[int]
>
>     def f(arg1, arg2):
>
>         # type: (int, int) -> Iterator[int]
>
> Either way, the type checker will determine that type of the function is
> `Callable[[int, int], Iterator[int]]`, and the only reason you'll ever care
> is if that type shows up in an error message.
>

I don't think you can the word 'Callable' to show up in an error message
unless it's part of the type as written somewhere. A name defined with
'def' is special and it shows up differently. (And so is a lambda.)


> >Regarding using Iterator[T] instead of Generator[..., ..., T], you are
> correct.
> >
>
> >Note that you *cannot* define a generator function as returning a
> *subclass* of Iterator/Generator;
>
> But you could define it as returning the superclass `Iterable`, right?


Yes.


> As I understand it, it's normal type variance, so any superclass will
> work; the only reason `Iterator` is special is that it happens to be
> simpler to specify than Generator and it's plausible that it isn't going to
> matter whether you've written a generator function or, say, a function that
> returns a list iterator.
>

Right.


> > there is no way to have a generator function instantiate some other
> class as its return value.
>
> If you really want that, you could always write a wrapper that forwards
> __next__, and a decorator that applies the wrapper. Can MyPy infer the type
> of the decorated function from the wrapped function and the decorator?
>

I think that's an open question. Your example below is complicated because
of the **args, *kw pattern.


>     # Can I leave this annotation off? And get one specialized to the
> actual
>     # argument types of the wrapped function? That would be cool.
>

You can't -- mypy never infers a function's type from its inner workings.

However, some Googlers are working on a tool that does infer types:
https://github.com/google/pytype

It's early days though (relatively speaking), and I don't think it handles
this case yet.


>     def my_iterating(func: Callable[Any, Iterator]) -> Callable[Any,
> MyIterator]
>

Alas, PEP 484 is not powerful enough to describe the relationship between
the input and output functions. You'd want to do something that uses a type
variable to capture all arguments together, so you could write something
like

T = TypeVar('T')
S = TypeVar('S')
def my_iterating(func: Callable[T, Iterator[S]]) -> Callable[T,
MyIterator[S]]:


>         @wraps(func)
>         def wrapper(*args, **kw):
>             return MyIterator(func(*args, **kw))
>         return wrapper
>
>     @my_iterating
>     def foo() -> Iterator[int]:
>         yield
>
>     x = foo()
>     x.bar()
>

The only reasonable way to do something like this without adding more
sophistication to PEP 484 would be to give up on the decorator and just
hardcode it using a pair of functions:

# API
def foo() -> MyIterator[int]:
    return MyIterator(_foo())

# Implementation
def _foo() -> Iterator[int]:
    yield 0

-- 
--Guido van Rossum (python.org/~guido)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20160120/e4efbc7b/attachment-0001.html>


More information about the Python-ideas mailing list