interfaces clases abstractas python abstract-class abc

interfaces - clases abstractas python



¿Por qué usar clases base abstractas en Python? (4)

Version corta

Los ABC ofrecen un mayor nivel de contrato semántico entre los clientes y las clases implementadas.

Versión larga

Hay un contrato entre una clase y sus llamadores. 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 impondría ese contrato. En Python, puede usar EAFP o introspección para confirmar que el objeto desconocido cumple este contrato esperado.

Pero también hay promesas semánticas de alto nivel en el contrato.

Por ejemplo, si hay un __str__() , se espera que devuelva una representación de cadena del objeto. Podría eliminar todo el contenido del objeto, comprometer la transacción y escupir una página en blanco de la impresora ... pero existe 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é debería hacer el método print() ? ¿Debería escribir el objeto en una impresora o una línea en la pantalla, o alguna otra cosa? Depende - necesita leer los comentarios para comprender el contrato completo aquí. Un código de cliente que simplemente comprueba que existe el método print() ha confirmado una parte del contrato: que se puede hacer una llamada a un método, pero no que exista 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 ejecutores de clase y los llamadores. No es solo una lista de nombres de métodos, sino una comprensión compartida de lo que esos métodos deberían hacer. Si heredas de este ABC, prometes seguir todas las reglas descritas en los comentarios, incluida la semántica del método print() .

El tipado de pato de Python tiene muchas ventajas en cuanto a la flexibilidad con respecto al tipado estático, pero no resuelve todos los problemas. Los ABC ofrecen una solución intermedia entre la forma libre de Python y la bondage-and-discipline de un lenguaje de tipo estático.

Al estar acostumbrado a las antiguas formas de escribir patos en Python, no pude entender la necesidad de ABC (clases base abstractas). La help es buena sobre cómo usarlos.

Traté de leer el razonamiento en el PEP , pero se me pasó por la cabeza. Si estuviera buscando un contenedor de secuencia mutable, buscaría __setitem__ , o más probablemente intenté usarlo ( EAFP ). No he encontrado un uso real para el módulo de numbers , que sí utiliza el abecedario, pero eso es lo más cercano que tengo para entender.

¿Alguien puede explicarme el razonamiento, por favor?


Hará que determinar si un objeto es compatible con un protocolo determinado sin tener que verificar la presencia de todos los métodos en el protocolo o sin desencadenar una excepción en lo profundo del territorio "enemigo" debido a la falta de soporte mucho más fácil.


La respuesta de @ Oddthinking no está mal, pero creo que se pierde la razón real y práctica Python tiene el ABC en un mundo de tipados de patos.

Los métodos abstractos son claros, pero en mi opinión no llenan ningún caso de uso que no esté cubierto por la tipificación de patos. El poder real de las clases base abstractas reside en la forma en que le permiten personalizar el comportamiento de isinstance y issubclass . ( __subclasshook__ es básicamente una API más amigable además de los ganchos __instancecheck__ y __subclasscheck__ de Python.) La adaptación de las construcciones incorporadas para trabajar en tipos personalizados es en gran parte parte de la filosofía de Python.

El código fuente de Python es ejemplar. Here muestra cómo collections.Container se define en la biblioteca estándar (al momento de escribir):

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 atributo __contains__ se considera una subclase de Container, incluso si no la subclasifica 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, ¡usted es una subclase! Los ABC proporcionan una forma formal de definir interfaces en Python, sin dejar de ser fiel al espíritu de la tipificación de patos. Además, esto funciona de una manera que rinde homenaje al Principio Abierto Cerrado .

El modelo de objetos de Python parece superficialmente similar al de un sistema OO más "tradicional" (me refiero a Java *), tenemos tus clases, tus objetos, tus métodos, pero cuando rascas la superficie encontrarás algo mucho más rico y mas flexible. Del mismo modo, la noción de Python de clases base abstractas puede ser reconocible para un desarrollador de Java, pero en la práctica tienen 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 considero que isinstance(x, collections.Iterable) es mucho más legible que hasattr(x, ''__iter__'') o una try...except equivalente try...except bloque. (Si no conocía Python, ¿cuál de esos tres haría más clara la intención del código?)

Encuentro que rara vez necesito escribir mi propio abecedario, prefiero confiar en la mecanografía de pato, 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 que espera ser extraído.

* sin entrar en el debate sobre si Java es un sistema OO "tradicional" ...

Adición : aunque una clase base abstracta puede anular el comportamiento de isinstance y issubclass , aún no ingresa al MRO de la subclase virtual. Este es un riesgo potencial para los clientes: no todos los objetos para los que isinstance(x, MyABC) == True tiene los métodos definidos en 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 es una de esas trampas de "simplemente no hagas eso" (¡de las cuales Python tiene relativamente pocas!): Evita definir el abecedario tanto con un __subclasshook__ como con métodos no abstractos. Además, debe hacer que su definición de __subclasshook__ coherente con el conjunto de métodos abstractos que define su ABC.


Una característica práctica del ABC es que si no implementa todos los métodos (y propiedades) necesarios, obtendrá un error al momento de la creación de instancias, en lugar de un AttributeError , posiblemente mucho más tarde, cuando realmente intente utilizar el método que falta.

from abc import ABCMeta, abstractmethod # python2 class Base(object): __metaclass__ = ABCMeta @abstractmethod def foo(self): pass @abstractmethod def bar(self): pass # python3 class Base(object, metaclass=ABCMeta): @abstractmethod def foo(self): pass @abstractmethod def bar(self): pass class Concrete(Base): def foo(self): pass # We forget to declare `bar` c = Concrete() # TypeError: "Can''t instantiate abstract class Concrete with abstract methods bar"

Ejemplo de https://dbader.org/blog/abstract-base-classes-in-python

Editar: para incluir la sintaxis de python3, gracias @PandasRocks