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)
When I run the code above, mypy brings me the following alert:
error: "ICommand" has no attribute "set_temperature" [attr-defined]
How do I do it when I need to call a method, but not every class that implements the ICommand interface has this method?
I tried to comment lines with #type ignore, but I would like to know a better way to handle this problem
Following mypy guidelines:
In cases where your code is too magical for mypy to understand, you can make a variable or parameter dynamically typed by explicitly giving it the type Any
source: https://mypy.readthedocs.io/en/stable/dynamic_typing.html
The best way to solve this problem is change get_command
method, in Aplicativo
class to:
from abc import ABC, abstractmethod
from typing import List, Any
...
class Aplicativo:
"""Class that represents an application that uses the command pattern to control an air conditioner"""
...
def get_command(self, comando_id: int) -> Any:
return self._comandos[comando_id]
...
Solving the issue with mypy