Как правильно обрабатывать ошибки mypy [attr-defined] из-за переходов, динамически добавляющих атрибуты is_*?

МРЭ

from transitions import Machine


class TradingSystem:
    def __init__(self):
        self.machine = Machine(model=self, states=['RUNNING'], initial='RUNNING')

    def check_running(self) -> None:
        if self.is_RUNNING():              
            print("System is running")

Пример использования

system = TradingSystem()
system.check_running()

Проблема

mypy transitions_mypy.py

выдает ошибку:

transitions_mypy.py:9: error: "TradingSystem" has no attribute "is_RUNNING"  [attr-defined]

Этого можно избежать, обойдя mypy, например добавив # type: ignore[attr-defined] в конце строки 9.

Но каков правильный путь? Лучше ли избегать обхода mypy? Возможно, определив атрибут вручную?

Предлагает ли pytransitions плагин mypy?

Brian61354270 28.05.2024 14:41

Согласно github.com/pytransitions/transitions/issues/574 «переходы 0.9.0 были опубликованы в PyPI и включают файлы-заглушки ввода». Но я не знаком с термином «плагин mypy».

James Hirschorn 28.05.2024 14:44

Mypy поддерживает систему плагинов, которая позволяет пакетам настраивать способ проверки mypy кода, который их использует: mypy.readthedocs.io/en/stable/… . Например, плагин mypy numpy. pytransition должен будет предоставить плагин, чтобы mypy знал о динамических атрибутах, которые он определяет.

Brian61354270 28.05.2024 14:56
Почему в 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 может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
3
224
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Итак, во-первых, причина этой проблемы заключается в том, что эта библиотека «переходов» действительно плохо себя ведет — внешнее изменение себя таким образом не совсем хорошая практика, например. что, если что-то уже использовало is_RUNNING, когда изменяло себя?

Тем не менее, есть способ решить вашу проблему без игнорирования типов, вам просто нужно смоделировать методы, которые генерируют переходы, через отдельный интерфейс. На данный момент я добавил сюда is_RUNNING, но вы можете добавить и другие методы:

from transitions import Machine

class TransitionsInterface:
    def is_RUNNING(self) -> bool:
        ...
    

class TradingSystem(TransitionsInterface):
    def __init__(self):
        self.machine = Machine(model=self, states=['RUNNING'], initial='RUNNING')

    def check_running(self) -> None:
        if self.is_RUNNING():              
            print("System is running")

system = TradingSystem()
system.check_running()

Надеюсь это поможет!

Насколько я понимаю, правильное решение от @brian61354270 — это то, что для переходов нужен плагин mypy. Но интерфейс кажется лучшим решением на данный момент.

James Hirschorn 09.06.2024 15:44

Я никогда не тестировал этот обходной путь, так как в конечном итоге я использовал attrs, который, как мне казалось, был просто вариацией этого метода. Но, как отмечает @aleneum, приведенный выше код даже не работает с моей модификацией.

James Hirschorn 17.06.2024 21:28
Ответ принят как подходящий

Ответ , предложенный @Mark, не будет работать, поскольку переходы не переопределяют уже существующие атрибуты модели, потому что это было бы неожиданным (неправильным) поведением (как указал @Mark). Если вы включите ведение журнала, переходы также сообщат вам, что:

import logging
logging.basicConfig(level=logging.DEBUG)

Вывод должен содержать:

WARNING:transitions.core:Model already contains an attribute 'is_RUNNING'. Skip binding.

Раньше я предлагал наследовать от Machine и переопределять Machine._checked_assignment, чтобы обойти эту защиту. Однако подход @james-hirschorn с использованием attrs , который был опубликован в трекере проблем с переходами, на мой взгляд, является лучшим решением:

from typing import Callable
from attrs import define, field
from transitions import Machine


@define(slots=False)
class TradingSystem:
    is_RUNNING: Callable[[], bool] = field(init=False)
    stop: Callable[[], None] = field(init=False)

    def __attrs_post_init__(self):
        self.machine = Machine(model=self, states=['RUNNING', 'STOPPED'], initial='RUNNING')
        self.machine.add_transition(trigger='stop', source='RUNNING', dest='STOPPED')

    def check_running(self) -> None:
        if self.is_RUNNING():  
            print("System is running")

    def stop_system(self) -> None:
        self.stop()
        print("System stopped")

# Example usage
system = TradingSystem()
system.check_running()
system.stop_system()

Это приводит к некоторым накладным расходам, поскольку вам придется дважды определять триггеры и удобные методы. Кроме того, информация о типе также является неполной, поскольку переходы также будут добавлять is_STOPPED и автоматические переходы, такие как to_STOPPED/RUNNING, или методы перехода «просмотр», такие как may_stop к TradingSystem во время выполнения.

Улучшение поддержки ввода переходов в настоящее время является открытым вопросом .

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