'mypy error: "Callable[[VarArg(Any), KwArg(Any)], Any]" has no attribute ***
I am defining a class that, if no color instance attribute is declared, sets it automatically based on the number of calls received.
Here below you can see a minimal working example:
from typing import Callable
import itertools
from plotly.express import colors as px_colors
COLOR_LIST = px_colors.qualitative.Alphabet
# Decorator
def color_setter(func: Callable):
"""Counts how many times `~MyClass` has been called and sets the
instance color accordingly.
Parameters
----------
func : Callable
Returns
-------
Callable
"""
def wrapper(*args, **kwargs):
wrapper.calls += 1
wrapper.color = next(wrapper.cycle_color_list)
# print(f'wrapper called {wrapper.calls} times')
return func(*args, **kwargs)
wrapper.calls = 0 # Adding # type: ignore here suppresses
# the error, but it is not a solution...
wrapper.cycle_color_list = itertools.cycle(COLOR_LIST) # same here
return wrapper
@color_setter
def _set_color(color: str) -> tuple:
"""Sets MyClass color and id.
Parameters
----------
color : str
Color variable, can be RGBA or HEX string format.
Returns
-------
tuple
MyClass color and call id
"""
if color is None:
color = _set_color.color
my_id = _set_color.calls
return color, my_id
class MyClass():
"""Example class.
"""
def __init__(
self,
color: str = None,
) -> None:
"""Define MyClass.
Parameters
----------
color : str, optional
RGBS or HEX string for color, by default None
"""
self.color, self.id = _set_color(color)
The code behaves as expected, but mypy returns the following two errors which I cannot figure out how to solve.
example.py:26: error: "Callable[[VarArg(Any), KwArg(Any)], Any]" has no attribute "calls"
wrapper.calls = 0
^
example.py:27: error: "Callable[[VarArg(Any), KwArg(Any)], Any]" has no attribute "cycle_color_list"
wrapper.cycle_color_list = itertools.cycle(COLOR_LIST)
^
Found 2 errors in 1 file (checked 1 source file)
Solution 1:[1]
Mypy is complaining about function attributes not being properly typed.
You define a wrapper function, which has the type Callable[[VarArg(Any), KwArg(Any)], Any]. This type does not come with the attributes calls and color on it. So accessing it causes an attribute error.
from typing import Callable, Optional
# Decorator
def color_setter_alt(func: Callable):
"""Counts how many times `~MyClass` has been called and sets the
instance color accordingly.
Parameters
----------
func : Callable
Returns
-------
Callable
"""
class Wrapper:
color: Optional[str]
def __init__(self):
# Wrapper is callable because it has the `__call__` method.
# Not being a plain function allows you to explicitly define the function attributes
self.calls = 0
self.cycle_color_list = itertools.cycle(COLOR_LIST)
def __call__(self, *args, **kwargs):
self.calls += 1
self.color = next(self.cycle_color_list)
# print(f'wrapper called {wrapper.calls} times')
return func(*args, **kwargs)
return Wrapper()
Another workaround will be using setattr and getattr
# Decorator
def color_setter(func: Callable):
"""Counts how many times `~MyClass` has been called and sets the
instance color accordingly.
Parameters
----------
func : Callable
Returns
-------
Callable
"""
def wrapper(*args, **kwargs):
setattr(wrapper, "calls", getattr(wrapper, "calls") + 1)
setattr(wrapper, "color", next(getattr(wrapper, "cycle_color_list")))
# print(f'wrapper called {wrapper.calls} times')
return func(*args, **kwargs)
setattr(wrapper, "calls", 0)
setattr(wrapper, "cycle_color_list", itertools.cycle(COLOR_LIST))
return wrapper
The advantage of this approach is that you can monkey patch additional attributes afterward without having to modify the original function. But the downside is that it does not provide you type hints.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
