La respuesta de @ Oddthinking no es incorrecta, pero creo que pierde la razón real y práctica por la que Python tiene ABC en un mundo de escritura de patos.
Los métodos abstractos son geniales, pero en mi opinión, realmente no llenan ningún caso de uso que no esté cubierto por la escritura de patos. El verdadero poder de las clases base abstractas radica en la forma en que le permiten personalizar el comportamiento de isinstance
yissubclass
. ( __subclasshook__
es básicamente una API más amigable además de Python __instancecheck__
y sus__subclasscheck__
ganchos). Adaptar construcciones integradas para trabajar en tipos personalizados es una parte muy importante de la filosofía de Python.
El código fuente de Python es ejemplar. Aquí es cómo collections.Container
se define en la biblioteca estándar (en el momento de la escritura):
class Container(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __contains__(self, x):
return False
@classmethod
def __subclasshook__(cls, C):
if cls is Container:
if any("__contains__" in B.__dict__ for B in C.__mro__):
return True
return NotImplemented
Esta definición de __subclasshook__
dice que cualquier clase con un __contains__
atributo se considera una subclase de Contenedor, incluso si no lo hace directamente. Entonces puedo escribir esto:
class ContainAllTheThings(object):
def __contains__(self, item):
return True
>>> issubclass(ContainAllTheThings, collections.Container)
True
>>> isinstance(ContainAllTheThings(), collections.Container)
True
En otras palabras, si implementa la interfaz correcta, ¡es una subclase! Los ABC proporcionan una forma formal de definir interfaces en Python, mientras se mantienen fieles al espíritu de tipear patos. Además, esto funciona de una manera que honra el Principio Abierto-Cerrado .
El modelo de objetos de Python se ve superficialmente similar al de un sistema OO más "tradicional" (con el que me refiero a Java *) - obtuvimos sus clases, sus objetos, sus métodos - pero cuando rasca la superficie encontrará algo mucho más rico y mas flexible. Del mismo modo, la noción de Python de clases base abstractas puede ser reconocida por un desarrollador de Java, pero en la práctica están destinadas a un propósito muy diferente.
A veces me encuentro escribiendo funciones polimórficas que pueden actuar sobre un solo elemento o una colección de elementos, y me parece isinstance(x, collections.Iterable)
mucho más legible que hasattr(x, '__iter__')
un try...except
bloque equivalente . (Si no conocieras Python, ¿cuál de esos tres aclararía la intención del código?)
Dicho esto, encuentro que rara vez necesito escribir mi propio ABC y normalmente descubro la necesidad de uno a través de la refactorización. Si veo una función polimórfica que realiza muchas comprobaciones de atributos, o muchas funciones que realizan las mismas comprobaciones de atributos, ese olor sugiere la existencia de un ABC a la espera de ser extraído.
* sin entrar en el debate sobre si Java es un sistema OO "tradicional" ...
Anexo : Aunque una clase base abstracta puede anular el comportamiento de isinstance
y issubclass
, aún no ingresa el MRO de la subclase virtual. Este es un escollo potencial para los clientes: no todos los objetos para los que se isinstance(x, MyABC) == True
han definido los métodos MyABC
.
class MyABC(metaclass=abc.ABCMeta):
def abc_method(self):
pass
@classmethod
def __subclasshook__(cls, C):
return True
class C(object):
pass
# typical client code
c = C()
if isinstance(c, MyABC): # will be true
c.abc_method() # raises AttributeError
Desafortunadamente, esta de esas trampas de "simplemente no hagas eso" (¡de las cuales Python tiene relativamente pocas!): Evita definir ABCs con __subclasshook__
métodos a y no abstractos. Además, debe hacer que su definición sea __subclasshook__
coherente con el conjunto de métodos abstractos que define su ABC.
__contains__
y una clase que heredacollections.Container
? En su ejemplo, en Python siempre hubo una comprensión compartida de__str__
. Implementar__str__
hace las mismas promesas que heredar de algunos ABC y luego implementar__str__
. En ambos casos puedes romper el contrato; No hay semánticas demostrables como las que tenemos en la escritura estática.