property - python interface
python: clase base abstracta ''__init__(): ¿inicialización o validación? (3)
class ABC
es una "clase base abstracta". class X
es su subclase.
Hay algún trabajo que debe hacerse en cualquier subclase de ABC
, que sea fácil de olvidar o hacer incorrectamente. Me gustaría que ABC.__init__()
ayude a detectar tales errores:
(1) comenzar ese trabajo, o (2) validarlo
Esto afecta si se llama a super().__init__()
al comienzo o al final de X.__init__()
.
Aquí hay un ejemplo simplificado para propósitos de ilustración:
Supongamos que cada subclase de ABC
debe tener un registry
atributos, y debe ser una lista. ABC.__init__()
puede (1) inicializar el registry
o (2) verificar que se haya creado correctamente. A continuación se muestra el código de ejemplo para cada enfoque.
Enfoque 1: inicializar en ABC
class ABC:
def __init__(self):
self.registry = []
class X:
def __init__(self):
super().__init__()
# populate self.registry here
...
Enfoque 2: validar en ABC
class ABC:
class InitializationFailure(Exception):
pass
def __init__(self):
try:
if not isinstance(self.registry, list):
raise ABC.InitializationError()
except AttributeError:
raise ABC.InitializationError()
class X:
def __init__(self):
self.registry = []
# populate self.registry here
...
super().__init__()
¿Cuál es un mejor diseño?
Ciertamente, uno prefiere el enfoque 1 al enfoque 2 (ya que el enfoque 2 relega la base a una interfaz de etiqueta en lugar de cumplir con la funcionalidad abstracta). Pero, el enfoque 1 no cumple, por sí solo, su objetivo de evitar que el desarrollador del subtipo se olvide de implementar la llamada super () correctamente, lo que garantiza la inicialización.
es posible que desee ver el patrón de "Fábrica" para aliviar la posibilidad de que los implementadores de subtipos olviden la inicialización. Considerar:
class AbstractClass(object):
''''''Abstract base class template, implementing factory pattern through
use of the __new__() initializer. Factory method supports trivial,
argumented, & keyword argument constructors of arbitrary length.''''''
__slots__ = ["baseProperty"]
''''''Slots define [template] abstract class attributes. No instance
__dict__ will be present unless subclasses create it through
implicit attribute definition in __init__() ''''''
def __new__(cls, *args, **kwargs):
''''''Factory method for base/subtype creation. Simply creates an
(new-style class) object instance and sets a base property. ''''''
instance = object.__new__(cls)
instance.baseProperty = "Thingee"
return instance
Esta clase base puede extenderse más trivialmente que en el enfoque 1, utilizando solo tres (3) líneas de código san-commment, de la siguiente manera:
class Sub(AbstractClass):
''''''Subtype template implements AbstractClass base type and adds
its own ''foo'' attribute. Note (though poor style, that __slots__
and __dict__ style attributes may be mixed.''''''
def __init__(self):
''''''Subtype initializer. Sets ''foo'' attribute. ''''''
self.foo = "bar"
Tenga en cuenta que aunque no llamamos al constructor de la superclase, la baseProperty se inicializará:
Python 2.6.1 (r261:67515, Jun 24 2010, 21:47:49)
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from TestFactory import *
>>> s = Sub()
>>> s.foo
''bar''
>>> s.baseProperty
''Thingee''
>>>
Como lo indica su comentario, la clase base AbstractClass no necesita usar ranuras , podría fácilmente definir los atributos "implícitamente" al establecerlos en su nuevo () inicializador. Por ejemplo:
instance.otherBaseProperty = "Thingee2"
funcionaría bien También tenga en cuenta que el inicializador de la clase base es compatible con los inicializadores triviales (no-arg) en sus subtipos, así como con inicializadores de argumentos de palabras clave y arugmentados de longitud variable. Recomiendo usar siempre este formulario, ya que no impone la sintaxis en el caso más simple (constructor trivial) pero permite la funcionalidad más compleja sin imponer mantenimiento.
El primero es el mejor diseño, ya que las subclases no necesitan saber que ha implementado el registro con una lista. Por ejemplo, podría ofrecer una función _is_in_registry
que toma un argumento y devuelve si el elemento está en el registro. Luego, puede cambiar la superclase y reemplazar la lista por un conjunto, ya que los elementos solo pueden aparecer una vez en el registro y no es necesario cambiar las subclases.
Además, es menos código: imagina que tienes 100 campos como este en ABC
y ABC
tiene 100 subclases como X
...
En el ejemplo que ha proporcionado, lo haría como en su enfoque 1. Me gustaría ver la clase ABC principalmente como un ayudante de implementación para X y otras clases que implementan una interfaz determinada, sin embargo. Dicha interfaz consta del atributo ''registro''.
Debe, lógicamente al menos, discernir entre la interfaz compartida por X y otras clases, y la clase básica que lo ayuda a implementarla. Es decir, defina por separado que hay una interfaz (por ejemplo, "ABC"), que expone una lista "registro". Luego, puede decidir factorizar la implementación de la interfaz como una clase de base común (conceptualmente una combinación) para los implementadores de la interfaz ABC, ya que hace que sea muy fácil introducir nuevas clases de implementación (además de X).
Edición: Con respecto a la protección contra los errores en la implementación de clases, apuntaría esto a través de pruebas unitarias. Creo que esto es más completo que tratar de explicar todo en su implementación :)