¿Por qué utilizar clases base abstractas en Python?

Resuelto Muhammad Alkarouri asked hace 14 años • 6 respuestas

Como estoy acostumbrado a las antiguas formas de escribir patos en Python, no entiendo la necesidad de ABC (clases base abstractas). La ayuda es buena sobre cómo usarlos.

Intenté leer el fundamento del PEP , pero se me pasó por alto. Si estuviera buscando un contenedor de secuencia mutable, lo buscaría __setitem__o, más probablemente, intentaría usarlo ( EAFP ). No he encontrado un uso en la vida real para el módulo de números , que sí usa ABC, pero eso es lo más cerca que tengo de entenderlo.

¿Alguien puede explicarme el motivo, por favor?

Muhammad Alkarouri avatar Aug 26 '10 05:08 Muhammad Alkarouri
Aceptado

La respuesta de @Oddthinking no es incorrecta, pero creo que pasa por alto la razón real y práctica por la que Python tiene el ABC en un mundo de escritura de pato.

Los métodos abstractos son interesantes, pero en mi opinión, realmente no cubren ningún caso de uso que no esté cubierto por la tipificación pato. El verdadero poder de las clases base abstractas radica en la forma en que le permiten personalizar el comportamiento de isinstanceyissubclass . ( __subclasshook__Es básicamente una API más amigable además de Python __instancecheck__y__subclasscheck__ ganchos). Adaptar construcciones integradas para trabajar en tipos personalizados es una gran parte de la filosofía de Python.

El código fuente de Python es ejemplar. Así es como collections.Containerse define en la biblioteca estándar (al momento de escribir este artículo):

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 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 implementas la interfaz correcta, ¡eres una subclase! Los ABC proporcionan una forma formal de definir interfaces en Python, mientras se mantienen fieles al espíritu de la escritura pato. Además, esto funciona de una manera que respeta el principio abierto-cerrado .

El modelo de objetos de Python parece superficialmente similar al de un sistema OO más "tradicional" (con lo que me refiero a Java*): tenemos 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 clases base abstractas de Python puede resultar reconocible para 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 encuentro isinstance(x, collections.Iterable)que son mucho más legibles que hasattr(x, '__iter__')un try...exceptbloque equivalente. (Si no conociera Python, ¿cuál de esos tres dejaría más clara la intención del código?)

Dicho esto, encuentro que rara vez necesito escribir mi propio ABC y normalmente descubro la necesidad de uno mediante la refactorización. Si veo una función polimórfica realizando muchas comprobaciones de atributos, o muchas funciones realizando las mismas comprobaciones de atributos, ese olor sugiere la existencia de un ABC esperando 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 isinstancey issubclass, todavía no ingresa al MRO de la subclase virtual. Este es un problema potencial para los clientes: no todos los objetos isinstance(x, MyABC) == Truetienen 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!): evite definir ABC con __subclasshook__métodos a y no abstractos. Además, debe hacer que su definición de __subclasshook__sea coherente con el conjunto de métodos abstractos que define su ABC.

Benjamin Hodgson avatar Oct 11 '2013 22:10 Benjamin Hodgson

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 quienes la llaman. La clase promete hacer ciertas cosas y tener ciertas propiedades.

Hay diferentes niveles en el contrato.

En un nivel muy bajo, el contrato podría 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 utilizar EAFP o introspección de tipos para confirmar que el objeto desconocido cumple con el contrato esperado.

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

Por ejemplo, si hay un __str__()método, se espera que devuelva una representación de cadena del objeto. Podría eliminar todo el contenido del objeto, confirmar la transacción y escupir una página en blanco de la impresora... pero existe un entendimiento común de lo que debe 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 print()hacer el método? ¿Debería escribir el objeto en una impresora o una línea en la pantalla, o algo más? Depende: debes leer los comentarios para comprender el contrato completo aquí. Un fragmento de código de cliente que simplemente verifica que el print()método existe ha confirmado parte del contrato: que se puede realizar una llamada al método, pero no que existe un acuerdo sobre la semántica de nivel superior de la llamada.

Definir una clase base abstracta (ABC) es una forma de producir un contrato entre los implementadores de la clase y los llamantes. No es sólo una lista de nombres de métodos, sino una comprensión compartida de lo que deberían hacer esos métodos. Si heredas de este ABC, prometes seguir todas las reglas descritas en los comentarios, incluida la semántica del print()método.

La escritura pato de Python tiene muchas ventajas en cuanto a 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 tipado estático.

Oddthinking avatar Aug 26 '2010 03:08 Oddthinking

Una característica útil de ABC es que si no implementa todos los métodos (y propiedades) necesarios, obtendrá un error al crear instancias, en lugar de un error AttributeError, potencialmente 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

cerberos avatar May 19 '2015 14:05 cerberos