Typing of method decorators not working properly [question]
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 20.6k
- Forks
- 3.3k
- PR merge metrics
- PR metrics pending
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')
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by running the complete Python reproducer in the issue with mypy and confirm the method-decorator errors. Then inspect how mypy types decorated methods and descriptor binding; done means determining whether the behavior is expected or narrowing it to a concrete typing change.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- developer-experience, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100