You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

咨询:如何解决Mypy无法识别接口子类特有方法的类型报错

解决命令模式中调用子类独有方法的Mypy类型检查问题

问题描述

我通过命令模式实现了空调控制的Python代码,但运行Mypy静态类型检查时抛出以下错误:

error: "ICommand" has no attribute "set_temperature" [attr-defined]

问题出在我需要调用仅部分ICommand实现类才具备的set_temperature方法,但不想通过#type ignore注释来规避类型检查,希望找到更优的处理方式。

实现代码

from abc import ABC, abstractmethod
from typing import List


class AirConditioner:
    """Class that represents an air conditioner"""

    def __init__(self, identify: str, state: bool, temperature: int):
        self.identify = identify
        self._state = state
        self._temperature = temperature

    def turn_on(self) -> None:
        self._state = True

    def turn_off(self) -> None:
        self._state = False

    def set_temperature(self, temperature: int) -> None:
        self._temperature = temperature

    def get_state(self) -> bool:
        return self._state

    def get_temperature(self) -> int:
        return self._temperature


class ICommand(ABC):
    """Interface that represents a command"""

    @abstractmethod
    def execute(self) -> None:
        pass

    @abstractmethod
    def undo(self) -> None:
        pass


class TurnOnAirConditioner(ICommand):
    """Class that represents a command to turn on an air conditioner"""

    def __init__(self, air_conditioner: AirConditioner):
        self._air_conditioner = air_conditioner

    def execute(self) -> None:
        self._air_conditioner.turn_on()

    def undo(self) -> None:
        self._air_conditioner.turn_off()


class ChangeTemperatureAirConditioner(ICommand):
    """Class that represents a command to change the temperature of an air conditioner"""

    def __init__(self, air_conditioner: AirConditioner):
        self._air_conditioner = air_conditioner
        self._temperature = air_conditioner.get_temperature()
        self._temperature_anterior = self._temperature

    def set_temperature(self, temperature: int) -> None:
        self._temperature_anterior = self._temperature
        self._temperature = temperature

    def execute(self) -> None:
        self._air_conditioner.set_temperature(self._temperature)

    def undo(self) -> None:
        self._air_conditioner.set_temperature(self._temperature_anterior)


class Aplicativo:
    """Class that represents an application that uses the command pattern to control an air conditioner"""

    def __init__(self) -> None:
        self._comandos: List[ICommand] = []

    def set_comando(self, comando_app: ICommand) -> int:
        self._comandos.append(comando_app)
        return len(self._comandos) - 1

    def get_command(self, comando_id: int) -> ICommand:
        return self._comandos[comando_id]

    def pressing_button(self, comando_id: int) -> None:
        self._comandos[comando_id].execute()


if __name__ == "__main__":
    app = Aplicativo()

    my_air_conditioner = AirConditioner("Air Conditioner", False, 26)

    change_temperature_air = ChangeTemperatureAirConditioner(my_air_conditioner)
    turn_on_ar = TurnOnAirConditioner(my_air_conditioner)

    ID_TURN_AIR_ON = app.set_comando(turn_on_ar)
    ID_CHANGE_AIR_TEMPERATURE = app.set_comando(change_temperature_air)

    app.pressing_button(ID_TURN_AIR_ON)
    comando = app.get_command(ID_CHANGE_AIR_TEMPERATURE)
    comando.set_temperature(25)

解决方案

方案1:使用类型断言(Type Assertion)

通过typing.cast明确告知Mypy当前对象的具体类型,既满足类型检查要求,又不会影响运行时逻辑:

from typing import cast

# 替换原调用代码
comando = cast(ChangeTemperatureAirConditioner, app.get_command(ID_CHANGE_AIR_TEMPERATURE))
comando.set_temperature(25)

这种方式简单直接,适合你明确知道命令类型的场景。

方案2:拆分命令接口

对命令接口进行细分,新增专门的温度控制命令接口,让类型系统更清晰:

# 新增温度命令接口
class ITemperatureCommand(ICommand):
    @abstractmethod
    def set_temperature(self, temperature: int) -> None:
        pass

# 修改ChangeTemperatureAirConditioner的继承关系
class ChangeTemperatureAirConditioner(ITemperatureCommand):
    # 原有代码保持不变

之后可以在调用时通过类型判断确保安全性:

comando = app.get_command(ID_CHANGE_AIR_TEMPERATURE)
if isinstance(comando, ITemperatureCommand):
    comando.set_temperature(25)
else:
    raise ValueError("该命令不支持温度设置操作")

这种方式符合面向接口设计原则,能让代码结构更健壮。

方案3:使用Protocol(Python 3.8+)

利用typing.Protocol定义一个“具备温度设置能力”的协议,无需修改原有类的继承关系,灵活性更高:

from typing import Protocol

class HasSetTemperature(Protocol):
    def set_temperature(self, temperature: int) -> None:
        pass

# 调用时做类型检查
comando = app.get_command(ID_CHANGE_AIR_TEMPERATURE)
if isinstance(comando, HasSetTemperature):
    comando.set_temperature(25)

Protocol采用“鸭子类型”的匹配逻辑,只要类实现了协议中的方法,就会被视为符合协议要求。


内容的提问来源于stack exchange,提问作者Magaren

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 01:43:07