Skip to content

Function returning a generic function #1551

Closed
@JukkaL

Description

@JukkaL
Collaborator

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

Activity

rwbarton

rwbarton commented on May 18, 2016

@rwbarton
Contributor

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

rwbarton

rwbarton commented on Jun 13, 2016

@rwbarton
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

JukkaL commented on Jun 13, 2016

@JukkaL
CollaboratorAuthor

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

sharmaeklavya2 commented on Jul 22, 2016

@sharmaeklavya2

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

jstasiak commented on Aug 10, 2016

@jstasiak
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

gnprice commented on Aug 11, 2016

@gnprice
Collaborator

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

added this to the 0.5 milestone on Aug 11, 2016
roganov

roganov commented on Oct 15, 2016

@roganov

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

gvanrossum commented on Oct 15, 2016

@gvanrossum
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 notifications@github.com
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)

32 remaining items

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @rwbarton@gnprice@jstasiak@ddfisher@JukkaL

        Issue actions

          Function returning a generic function · Issue #1551 · python/mypy