También se podría hacer un método raise NotImplementedError() dentro del hijo de un método de @abstractmethodclase base decorado.
Imagine escribir un script de control para una familia de módulos de medición (dispositivos físicos). La funcionalidad de cada módulo se define de manera estricta, implementando solo una función dedicada: una podría ser una matriz de relés, otra un DAC o ADC multicanal, otra un amperímetro, etc.
Gran parte de los comandos de bajo nivel en uso se compartirían entre los módulos, por ejemplo, para leer sus números de identificación o enviarles un comando. Veamos qué tenemos en este punto:
Clase base
from abc import ABC, abstractmethod
class Generic(ABC):
''' Base class for all measurement modules. '''
def __init__(self):
def _read_ID(self):
def _send_command(self, value):
Verbos compartidos
Entonces nos damos cuenta de que gran parte de los verbos de comando específicos del módulo y, por lo tanto, la lógica de sus interfaces también se comparte. Aquí hay 3 verbos diferentes cuyo significado se explica por sí mismo considerando una serie de módulos de destino.
get(channel)
relé: activa el estado de encendido / apagado del reléchannel
DAC: activa el voltaje de salidachannel
ADC: activa el voltaje de entradachannel
enable(channel)
relé: habilita el uso del relé enchannel
DAC: habilita el uso del canal de salida enchannel
ADC: habilita el uso del canal de entrada enchannel
set(channel)
relé: enciende channel/ apaga el relé
DAC: configure el voltaje de salida enchannel
ADC: hmm ... no se me ocurre nada lógico .
Los verbos compartidos se convierten en verbos obligatorios
Yo diría que hay un caso sólido para que los verbos anteriores se compartan entre los módulos, ya que vimos que su significado es evidente para cada uno de ellos. Continuaría escribiendo mi clase base Genericasí:
class Generic(ABC):
@abstractmethod
def get(self, channel):
pass
@abstractmethod
def enable(self, channel):
pass
@abstractmethod
def set(self, channel):
pass
Subclases
Ahora sabemos que todas nuestras subclases tendrán que definir estos métodos. Veamos cómo se vería para el módulo ADC:
class ADC(Generic):
def __init__(self):
super().__init__()
def get(self, channel):
def enable(self, channel):
Puede que ahora te estés preguntando:
¡Pero esto no funcionará para el módulo ADC ya setque no tiene sentido allí ya que acabamos de ver esto arriba!
Tienes razón: no implementar no setes una opción, ya que Python dispararía el siguiente error cuando intentas crear una instancia de tu objeto ADC.
TypeError: Can't instantiate abstract class 'ADC' with abstract methods 'set'
Entonces debes implementar algo, porque creamos setun verbo forzado (también conocido como '@abstractmethod'), que es compartido por otros dos módulos pero, al mismo tiempo, tampoco debes implementar nada, ya
setque no tiene sentido para este módulo en particular.
NotImplementedError al rescate
Al completar la clase ADC de esta manera:
class ADC(Generic):
def set(self, channel):
raise NotImplementedError("Can't use 'set' on an ADC!")
Estás haciendo tres cosas muy buenas a la vez:
- Está protegiendo a un usuario de la emisión errónea de un comando ('set') que no está (¡y no debería!) Implementarse para este módulo.
- Les está diciendo explícitamente cuál es el problema (consulte el enlace de TemporalWolf sobre 'Excepciones desnudas' para saber por qué esto es importante)
- Está protegiendo la implementación de todos los demás módulos para los que los verbos forzados
tienen sentido. Es decir, se asegura de que los módulos para los que estos verbos tienen sentido implementarán estos métodos y que lo harán utilizando exactamente estos verbos y no algunos otros nombres ad-hoc.