Ошибка mypy: «Callable[[VarArg(Any), KwArg(Any)], Any]» не имеет атрибута ***

Я определяю класс, который, если атрибут экземпляра color не объявлен, устанавливает его автоматически в зависимости от количества полученных вызовов.

Ниже вы можете увидеть минимальный рабочий пример:

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)

Код ведет себя так, как ожидалось, но mypy возвращает следующие две ошибки, которые я не могу понять, как решить.

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)
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
0
65
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Mypy жалуется на то, что атрибуты функции неправильно набран.

Вы определяете функцию-оболочку, которая имеет тип Callable[[VarArg(Any), KwArg(Any)], Any]. Этот тип не имеет атрибутов calls и color. Таким образом, доступ к нему вызывает ошибку атрибута.

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()

Другой обходной путь будет использовать setattr и 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

Преимущество этого подхода заключается в том, что вы можете впоследствии исправить дополнительные атрибуты, не изменяя исходную функцию. Но недостатком является то, что он не дает подсказок.

Спасибо за ваш ответ! Даже после исправления опечатки (wrapper.calls -> Wrapper.calls) решение 1 не работает и выдает __init__() takes 1 positional argument but 2 were given, а решение 2 фактически решает ошибку mypy, но потом pylint жалуется ([E1101(no-member), color_setter.wrapper] Function 'wrapper' has no 'calls' member).

Pietro D'Antuono 17.03.2022 11:28

@PietroD'Antuono Извините за опечатки! Я обновил ответ. Возвращаемое значение декоратора должно быть Wrapper() вместо Wrapper, и доступ к этим атрибутам должен осуществляться через self. Это должно работать сейчас. Что касается второго решения, нам также нужно использовать setattr и getattr в теле функции-обертки.

PIG208 17.03.2022 16:29

Другие вопросы по теме