-
-
Notifications
You must be signed in to change notification settings - Fork 3k
Open
Description
Hi. It seems that the typing of decorators is problematic with functions inside classes.
Suppose we want to see how many times a function has been called, we can write a simple decorator:
def counter(func):
def wrapper(*args, **kwargs):
wrapper.count += 1
return func(*args, **kwargs)
wrapper.count = 0
return wrapper
and use it like:
@counter
def greet(name: str) -> str:
return f'hello, {name}'
print(greet('John'))
print(f'greet() called {greet.count} times')
To type this counter
decorator, we can:
T = TypeVar('T', bound=Callable[..., Any])
class CountedFunction(Protocol[T]):
__call__: T
count: int
def counter(func: T) -> CountedFunction[T]: ...
This is fine with functions outside classes. However, it is not with a class function method:
class A:
@counter
def greet(self, name: str) -> str:
return f'hello, {name}'
a = A()
print(a.greet('John')) # error
print(f'greet() called {a.greet.count} times')
We get static errors from mypy, although the code can be run without any runtime errors:
test.py:37: error: Too few arguments for "greet" of "A"
test.py:37: error: Argument 1 to "greet" of "A" has incompatible type "str"; expected "A"
Full code (click to expand)
from typing import Any, Protocol, TypeVar, Callable, cast
T = TypeVar('T', bound=Callable[..., Any])
class CountedFunction(Protocol[T]):
__call__: T
count: int
def counter(func: T) -> CountedFunction[T]:
def wrapper(*args, **kwargs):
wrapper.count += 1
return func(*args, **kwargs)
wrapper.count = 0 # type: ignore
return cast(CountedFunction[T], wrapper)
# usage with a normal function
@counter
def greet(name: str) -> str:
return f'hello, {name}'
print(greet('John')) # OK
print(f'greet() called {greet.count} times')
# usage with a class function
class A:
@counter
def greet(self, name: str) -> str:
return f'hello, {name}'
a = A()
print(a.greet('John')) # error
print(f'greet() called {a.greet.count} times')
pbeschetnov, rmorshea, k0t3n and Davmuz
Metadata
Metadata
Assignees
Labels
No labels