Version corta
Los ABC ofrecen un mayor nivel de contrato semántico entre los clientes y las clases implementadas.
Versión larga
Existe un contrato entre una clase y sus interlocutores. La clase promete hacer ciertas cosas y tener ciertas propiedades.
Hay diferentes niveles para el contrato.
En un nivel muy bajo, el contrato puede incluir el nombre de un método o su número de parámetros.
En un lenguaje de tipo estático, el compilador haría cumplir ese contrato. En Python, puede usar EAFP o escribir introspección para confirmar que el objeto desconocido cumple con este contrato esperado.
Pero también hay promesas semánticas de alto nivel en el contrato.
Por ejemplo, si hay un __str__()
método, se espera que devuelva una representación de cadena del objeto. Se puede borrar todo el contenido del objeto, confirmar la transacción y escupió una página en blanco de la impresora ... pero hay un entendimiento común de lo que debería hacer, descrito en el manual de Python.
Ese es un caso especial, donde el contrato semántico se describe en el manual. ¿Qué debe hacer el print()
método? ¿Debería escribir el objeto en una impresora o una línea en la pantalla, o algo más? Depende: debe leer los comentarios para comprender el contrato completo aquí. Una parte del código del cliente que simplemente verifica que el print()
método existe ha confirmado parte del contrato: que se puede hacer una llamada al método, pero no que haya un acuerdo sobre la semántica de nivel superior de la llamada.
La definición de una clase base abstracta (ABC) es una forma de producir un contrato entre los implementadores de la clase y las personas que llaman. No es solo una lista de nombres de métodos, sino una comprensión compartida de lo que deberían hacer esos métodos. Si hereda de este ABC, promete seguir todas las reglas descritas en los comentarios, incluida la semántica del print()
método.
La escritura de pato de Python tiene muchas ventajas en flexibilidad sobre la escritura estática, pero no resuelve todos los problemas. Los ABC ofrecen una solución intermedia entre la forma libre de Python y la esclavitud y disciplina de un lenguaje de tipo estático.
__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.collections.Container
es un caso degenerado, que solo incluye\_\_contains\_\_
, y solo significa la convención predefinida. Usar un ABC no agrega mucho valor por sí mismo, estoy de acuerdo. Sospecho que se agregó para permitir (por ejemplo)Set
heredar de él. Para cuando lleguesSet
, pertenecer repentinamente al ABC tiene una semántica considerable. Un artículo no puede pertenecer a la colección dos veces. Eso NO es detectable por la existencia de métodos.Set
es un mejor ejemplo queprint()
. Intentaba encontrar un nombre de método cuyo significado fuera ambiguo, y no podía ser asimilado solo por el nombre, por lo que no podía estar seguro de que haría lo correcto solo por su nombre y el manual de Python.Set
como ejemplo en lugar deprint
?Set
tiene mucho sentido, @Oddthinking.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):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: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 quehasattr(x, '__iter__')
untry...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
yissubclass
, 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 seisinstance(x, MyABC) == True
han definido los métodosMyABC
.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.fuente
isinstance(x, collections.Iterable)
es más claro para mí, y conozco Python.C
subclase elimine (o arruine más allá de la reparación) loabc_method()
heredadoMyABC
. La principal diferencia es que es la superclase la que está arruinando el contrato de herencia, no la subclase.Container.register(ContainAllTheThings)
para que el ejemplo dado funcione?__subclasshook__
es "cualquier clase que satisfaga este predicado se considera una subclase a los efectosisinstance
y lasissubclass
comprobaciones, independientemente de si se registró en el ABC o de si es una subclase directa ". Como dije en la respuesta, si implementas la interfaz correcta, ¡eres una subclase!Una característica útil de ABC es que si no implementa todos los métodos (y propiedades) necesarios, obtiene un error al crear una instancia, en lugar de un
AttributeError
, potencialmente mucho más tarde, cuando realmente intenta utilizar el método faltante.Ejemplo de https://dbader.org/blog/abstract-base-classes-in-python
Editar: para incluir la sintaxis de python3, gracias @PandasRocks
fuente
Hará mucho más fácil determinar si un objeto admite un protocolo dado sin tener que verificar la presencia de todos los métodos en el protocolo o sin desencadenar una excepción en el territorio "enemigo" debido a la falta de soporte.
fuente
El método abstracto se asegura de que cualquier método que esté llamando en la clase principal tenga que aparecer en la clase secundaria. A continuación se presentan formas noraml de llamar y usar el resumen. El programa escrito en python3
Forma normal de llamar
Con el método abstracto
Como no se llama a methodtwo en la clase secundaria, obtuvimos un error. La implementación adecuada está debajo
fuente