Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Function returning a generic function #1551

Closed
JukkaL opened this issue May 18, 2016 · 13 comments
Closed

Function returning a generic function #1551

JukkaL opened this issue May 18, 2016 · 13 comments
Labels
bug mypy got something wrong priority-1-normal

Comments

@JukkaL
Copy link
Collaborator

JukkaL commented May 18, 2016

Mypy doesn't support a function that tries to return a generic callable. I wonder if mypy could make this code work properly:

from typing import TypeVar, Callable, Any

T = TypeVar('T', bound=Callable[..., Any])

def deco() -> Callable[[T], T]:
    def inner(x):
        return x
    return inner

@deco()
def g(x: str) -> int: ...

g('') # should be fine? currently "Argument 1 to "g" has incompatible type "str"; expected None"
g(1) # should be an error
@rwbarton
Copy link
Contributor

This is related to my comments at #1317, but maybe worth keeping open as a separate issue.

@rwbarton
Copy link
Contributor

As Jukka and I discussed at PyCon, this situation where a type variable appears only in the result type of a generic callable could be handled by "floating in" the type variable to the result type: instead of

def [T] () -> def (T) -> T

give deco the type

def () -> def [T] (T) -> T

I wonder how often this arises in practice?

@JukkaL
Copy link
Collaborator Author

JukkaL commented Jun 13, 2016

The only case where I remember having seen this is in a decorator that is used like this:

@memoize(cache_size=200)
def do_stuff(): ...

The decorator, when called, returns a generic function T -> T that preserves the signature of the decorated function.

@sharmaeklavya2
Copy link

At Zulip, we're using such decorator-returning functions a lot. One of these decorator-returning functions, named cache_with_keys, is applied to dozens of functions, many of which are called hundreds of times (see zulip/zulip#1348). It'll be great to see this issue fixed since a lot of our code is not properly type checked because of this.

@jstasiak
Copy link
Contributor

I have another example from our codebase:

@app.route('/')
@rate_limit(30, timedelta(seconds=60))
def index() -> Response:
    # ...

Both of the decorators returned by app.route() and rate_limit() at lest formally preserve the signature of the callable being decorated.

Sadly I don't have enough knowledge to help with this myself.

@gnprice
Copy link
Collaborator

gnprice commented Aug 11, 2016

This seems like something we'll really want to fix, but it's not immediately clear yet what the fix looks like.

@gnprice gnprice added this to the 0.5 milestone Aug 11, 2016
@roganov
Copy link

roganov commented Oct 15, 2016

As a workaround, rewriting nested functions to a class seem to work:

from functools import wraps
from typing import TypeVar, Callable, Any, cast

TFun = TypeVar('TFun', bound=Callable[..., Any])

class log_calls:
    def __init__(self, prefix: str) -> None:
        self.prefix = prefix

    def __call__(self, f: TFun) -> TFun:
        prefix = self.prefix
        @wraps(f)
        def wrapper(*args, **kwargs) -> Any:
            print('{}: {!r} {!r}'.format(prefix, args, kwargs))
            return f(*args, **kwargs)
        return cast(TFun, wrapper)

@log_calls('Calling foo')
def foo(x: str) -> str:
    return x

reveal_type(foo)

@gvanrossum
Copy link
Member

Oooh, very clever! I can't say I personally understand how it works, and we
should still fix the original error, but this looks like a useful
workaround. Thanks!

On Sat, Oct 15, 2016 at 4:28 AM, Yegor Roganov [email protected]
wrote:

As a workaround, rewriting nested functions to a class seem to work:

from functools import wrapsfrom typing import TypeVar, Callable, Any, cast

TFun = TypeVar('TFun', bound=Callable[..., Any])
class log_calls:
def init(self, prefix: str) -> None:
self.prefix = prefix

def __call__(self, f: TFun) -> TFun:
    prefix = self.prefix
    @wraps(f)
    def wrapper(*args, **kwargs) -> Any:
        print('{}: {!r} {!r}'.format(prefix, args, kwargs))
        return f(*args, **kwargs)
    return cast(TFun, wrapper)

@log_calls('Calling foo')def foo(x: str) -> str:
return x

reveal_type(foo)


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
#1551 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/ACwrMntwUxGKXBJXiFRnuVKRAye7x5Rxks5q0LjDgaJpZM4IhSDU
.

--Guido van Rossum (python.org/~guido)

ambv referenced this issue in python/typeshed Dec 24, 2016
Starting with python/mypy#2521 mypy is performing stricter function signature
checks.

This makes the stubs diverge from the actual implementation but makes the stubs
internally consistent.  Since this is an actual typing issue in the base
implementation, we need to defer to the original authors to fix it.

Sadly, in this case the breakage is rather fundamental and unlikely to get
fixed by upstream. Consider:

```
  class AWSAuthConnection(object):
    def make_request(self, method, path, headers=None, data='', host=None,
      auth_path=None, sender=None, override_num_retries=None,
      params=None, retry_handler=None): ...

  class AWSQueryConnection(AWSAuthConnection):
    def make_request(self, action, params=None, path='/', verb='GET'): ...
```

Hence, until we have a workaround for the error produced by Mypy, we're
excluding those stubs from being tested against.
gvanrossum added a commit to python/typeshed that referenced this issue Jan 2, 2017
Reverts #804.

Reason: until python/mypy#1551 is fixed this gives an error whenever @Skip() is used.

Specifically see #804 (comment).
@sixolet
Copy link
Collaborator

sixolet commented Mar 24, 2017

This looks fun and important. It'll be a good warm-up to all the rest of the features we need to get decorators working right. claim

@gvanrossum gvanrossum assigned gvanrossum and unassigned gvanrossum Mar 24, 2017
@gvanrossum gvanrossum removed this from the 0.5 milestone Mar 29, 2017
gvanrossum added a commit to python/typeshed that referenced this issue Mar 29, 2017
sixolet added a commit to sixolet/mypy that referenced this issue Mar 29, 2017
Instead of TypeQuery always returning a boolean and having the strategy be an
enum, the strategy is now a Callable describing how to combine partial results,
and the two default strategies are plain old funcitons.

To preserve the short-circuiting behavior of the previous code, this PR uses an
exception.

This is a pure refactor that I am using in my experimentation regarding fixing
python#1551.  It should result in exactly no
change to current behavior. It's separable from the other things I'm
experimenting with, so I'm filing it as a separate pull request now. It enables
me to rewrite the code that pulls type variables out of types as a TypeQuery.

Consider waiting to merge this PR until I have some code that uses it ready for
review.  Or merge it now, if you think it's a pleasant cleanup instead of an
ugly complication.  I'm of two minds on that particular question.
sixolet added a commit to sixolet/mypy that referenced this issue Apr 1, 2017
Instead of TypeQuery always returning a boolean and having the strategy be an
enum, the strategy is now a Callable describing how to combine partial results,
and the two default strategies are plain old funcitons.

To preserve the short-circuiting behavior of the previous code, this PR uses an
exception.

This is a pure refactor that I am using in my experimentation regarding fixing
python#1551.  It should result in exactly no
change to current behavior. It's separable from the other things I'm
experimenting with, so I'm filing it as a separate pull request now. It enables
me to rewrite the code that pulls type variables out of types as a TypeQuery.

Consider waiting to merge this PR until I have some code that uses it ready for
review.  Or merge it now, if you think it's a pleasant cleanup instead of an
ugly complication.  I'm of two minds on that particular question.
sixolet added a commit to sixolet/mypy that referenced this issue Apr 1, 2017
Instead of TypeQuery always returning a boolean and having the strategy be an
enum, the strategy is now a Callable describing how to combine partial results,
and the two default strategies are plain old funcitons.

To preserve the short-circuiting behavior of the previous code, this PR uses an
exception.

This is a pure refactor that I am using in my experimentation regarding fixing
python#1551.  It should result in exactly no
change to current behavior. It's separable from the other things I'm
experimenting with, so I'm filing it as a separate pull request now. It enables
me to rewrite the code that pulls type variables out of types as a TypeQuery.

Consider waiting to merge this PR until I have some code that uses it ready for
review.  Or merge it now, if you think it's a pleasant cleanup instead of an
ugly complication.  I'm of two minds on that particular question.
sixolet added a commit to sixolet/mypy that referenced this issue Apr 3, 2017
Instead of TypeQuery always returning a boolean and having the strategy be an
enum, the strategy is now a Callable describing how to combine partial results,
and the two default strategies are plain old funcitons.

To preserve the short-circuiting behavior of the previous code, this PR uses an
exception.

This is a pure refactor that I am using in my experimentation regarding fixing
python#1551.  It should result in exactly no
change to current behavior. It's separable from the other things I'm
experimenting with, so I'm filing it as a separate pull request now. It enables
me to rewrite the code that pulls type variables out of types as a TypeQuery.

Consider waiting to merge this PR until I have some code that uses it ready for
review.  Or merge it now, if you think it's a pleasant cleanup instead of an
ugly complication.  I'm of two minds on that particular question.
@sixolet
Copy link
Collaborator

sixolet commented Apr 3, 2017

I just opened a pull request #3113 that fixes this. It's still WIP, but I should link this issue in.

gvanrossum pushed a commit that referenced this issue Apr 12, 2017
Instead of TypeQuery always returning a boolean and having the strategy be an
enum, the strategy is now a Callable describing how to combine partial results,
and the two default strategies are plain old funcitons.

To preserve the short-circuiting behavior of the previous code, this PR uses an
exception.

This is a pure refactor that I am using in my experimentation regarding fixing
#1551.  It should result in exactly no
change to current behavior. It's separable from the other things I'm
experimenting with, so I'm filing it as a separate pull request now. It enables
me to rewrite the code that pulls type variables out of types as a TypeQuery.

Consider waiting to merge this PR until I have some code that uses it ready for
review.  Or merge it now, if you think it's a pleasant cleanup instead of an
ugly complication.  I'm of two minds on that particular question.
@gvanrossum
Copy link
Member

@sixolet, Is this fixed by your #3113?

@sixolet
Copy link
Collaborator

sixolet commented Apr 22, 2017

This is the email I meant to reply to saying yes I think it's fixed. Sorry, I was getting off a plane on 4h sleep.

@gvanrossum
Copy link
Member

NP! We've had quite a busy week. I'll tell you the stories some time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong priority-1-normal
Projects
None yet
Development

No branches or pull requests

9 participants