Diferencia entre clase abstracta e interfaz en Python

Respuestas:

618

Lo que verá a veces es lo siguiente:

class Abstract1( object ):
    """Some description that tells you it's abstract,
    often listing the methods you're expected to supply."""
    def aMethod( self ):
        raise NotImplementedError( "Should have implemented this" )

Debido a que Python no tiene (y no necesita) un contrato de interfaz formal, la distinción de estilo Java entre abstracción e interfaz no existe. Si alguien se esfuerza por definir una interfaz formal, también será una clase abstracta. Las únicas diferencias estarían en la intención declarada en la cadena de documentación.

Y la diferencia entre el resumen y la interfaz es una cosa desgarradora cuando tienes que escribir en un pato.

Java usa interfaces porque no tiene herencia múltiple.

Debido a que Python tiene herencia múltiple, también puede ver algo como esto

class SomeAbstraction( object ):
    pass # lots of stuff - but missing something

class Mixin1( object ):
    def something( self ):
        pass # one implementation

class Mixin2( object ):
    def something( self ):
        pass # another

class Concrete1( SomeAbstraction, Mixin1 ):
    pass

class Concrete2( SomeAbstraction, Mixin2 ):
    pass

Esto utiliza una especie de superclase abstracta con mixins para crear subclases concretas que son disjuntas.

S.Lott
fuente
55
S. Lott, ¿quiere decir que debido a que el tipeo de pato la distinción entre has-a (interfaz) e is-a (herencia) no es sustancial?
Lorenzo
3
La diferencia entre el resumen y la interfaz es una cuestión de corte de pelo cuando tienes que escribir pato. No sé lo que significa "sustancial". Es "real", tiene sustancia, desde una perspectiva de diseño. Pero desde la perspectiva del lenguaje puede que no haya soporte. Podría adoptar convenciones para distinguir entre una clase abstracta y una definición de clase de interfaz en Python.
S.Lott
26
@ L.DeLeo: ¿está seguro de que su noción de has-a vs. is-a es correcta? En general, veo la distinción como has-a = variable miembro vs. is-a = herencia (clase o interfaz principal). Piense Comparable o List en Java; esas son relaciones is-a, independientemente de si son interfaces o clases abstractas.
dimo414
43
NotImplementedError("Class %s doesn't implement aMethod()" % (self.__class__.__name__))es un mensaje de error más informativo :)
naught101
99
@Lorenzo una relación has-a no tiene nada que ver con la herencia, el tipeo de patos, las interfaces y las clases abstractas (las cuatro se refieren a relaciones is-a).
Karl Richter
197

¿Cuál es la diferencia entre clase abstracta e interfaz en Python?

Una interfaz, para un objeto, es un conjunto de métodos y atributos en ese objeto.

En Python, podemos usar una clase base abstracta para definir y aplicar una interfaz.

Usando una clase base abstracta

Por ejemplo, supongamos que queremos usar una de las clases base abstractas del collectionsmódulo:

import collections
class MySet(collections.Set):
    pass

Si intentamos usarlo, obtenemos un TypeErrorporque la clase que creamos no admite el comportamiento esperado de los conjuntos:

>>> MySet()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class MySet with abstract methods
__contains__, __iter__, __len__

Por lo tanto estamos obligados a implementar al menos __contains__ , __iter__y __len__. Usemos este ejemplo de implementación de la documentación :

class ListBasedSet(collections.Set):
    """Alternate set implementation favoring space over speed
    and not requiring the set elements to be hashable. 
    """
    def __init__(self, iterable):
        self.elements = lst = []
        for value in iterable:
            if value not in lst:
                lst.append(value)
    def __iter__(self):
        return iter(self.elements)
    def __contains__(self, value):
        return value in self.elements
    def __len__(self):
        return len(self.elements)

s1 = ListBasedSet('abcdef')
s2 = ListBasedSet('defghi')
overlap = s1 & s2

Implementación: creación de una clase base abstracta

Podemos crear nuestra propia Clase base abstracta configurando la metaclase abc.ABCMetay utilizando el abc.abstractmethoddecorador en métodos relevantes. La metaclase agregará las funciones decoradas al __abstractmethods__atributo, evitando la creación de instancias hasta que se definan.

import abc

Por ejemplo, "effable" se define como algo que se puede expresar en palabras. Digamos que queremos definir una clase base abstracta que sea effable, en Python 2:

class Effable(object):
    __metaclass__ = abc.ABCMeta
    @abc.abstractmethod
    def __str__(self):
        raise NotImplementedError('users must define __str__ to use this base class')

O en Python 3, con el ligero cambio en la declaración de metaclase:

class Effable(object, metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def __str__(self):
        raise NotImplementedError('users must define __str__ to use this base class')

Ahora, si intentamos crear un objeto effable sin implementar la interfaz:

class MyEffable(Effable): 
    pass

e intentar instanciarlo:

>>> MyEffable()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class MyEffable with abstract methods __str__

Nos dicen que no hemos terminado el trabajo.

Ahora si cumplimos proporcionando la interfaz esperada:

class MyEffable(Effable): 
    def __str__(self):
        return 'expressable!'

entonces podemos usar la versión concreta de la clase derivada de la abstracta:

>>> me = MyEffable()
>>> print(me)
expressable!

Hay otras cosas que podríamos hacer con esto, como registrar subclases virtuales que ya implementan estas interfaces, pero creo que eso está más allá del alcance de esta pregunta. abcSin embargo, los otros métodos demostrados aquí tendrían que adaptar este método usando el módulo para hacerlo.

Conclusión

Hemos demostrado que la creación de una clase base abstracta define interfaces para objetos personalizados en Python.

Aaron Hall
fuente
101

Python> = 2.6 tiene clases base abstractas .

Las clases base abstractas (ABC abreviadas) complementan la tipificación de pato al proporcionar una forma de definir interfaces cuando otras técnicas como hasattr () serían torpes. Python viene con muchos ABC integrados para estructuras de datos (en el módulo de colecciones), números (en el módulo de números) y flujos (en el módulo io). Puede crear su propio ABC con el módulo abc.

También está el módulo Zope Interface , que es utilizado por proyectos fuera de zope, como twisted. No estoy realmente familiarizado con eso, pero hay una página wiki aquí que podría ayudar.

En general, no necesita el concepto de clases abstractas o interfaces en python (editado - vea la respuesta de S.Lott para más detalles).

JimB
fuente
2
¿Qué ganas al usar ABC en Python?
CpILL
38

Python realmente no tiene ninguno de los dos conceptos.

Utiliza la escritura de pato, lo que eliminó la necesidad de interfaces (al menos para la computadora :-))

Python <= 2.5: las clases base obviamente existen, pero no existe una forma explícita de marcar un método como 'virtual puro', por lo que la clase no es realmente abstracta.

Python> = 2.6: existen clases base abstractas ( http://docs.python.org/library/abc.html ). Y le permite especificar métodos que deben implementarse en subclases. No me gusta mucho la sintaxis, pero la función está ahí. La mayoría de las veces, probablemente sea mejor usar la escritura de pato desde el lado del cliente 'usar'.

Douglas Leeder
fuente
3
Python 3.0 agrega clases base abstractas reales. Se utilizan en el módulo de colecciones, así como en otros lugares. docs.python.org/3.0/library/abc.html
Lara Dougan
Sería útil una referencia sobre por qué la escritura de pato elimina la necesidad de interfaces. No me parece obvio que escribir pato, lo que entiendo como la capacidad de "pinchar" cualquier método o atributo en cualquier objeto, significaría que no necesita especificar los comportamientos necesarios (y obtener el compilador para recordarle implementarlos), que es cómo entiendo las clases base abstractas.
Reb.Cabin
Es menos el tipo de pato que el soporte para herencia múltiple lo que elimina la línea artificial entre la interfaz y la clase abstracta que, por ejemplo, Java ha dibujado.
masi
35

En una forma más básica de explicar: una interfaz es como una bandeja de panecillos vacía. Es un archivo de clase con un conjunto de definiciones de métodos que no tienen código.

Una clase abstracta es lo mismo, pero no todas las funciones deben estar vacías. Algunos pueden tener código. No está estrictamente vacío.

Por qué diferenciar: no hay mucha diferencia práctica en Python, pero a nivel de planificación para un proyecto grande, podría ser más común hablar de interfaces, ya que no hay código. Especialmente si está trabajando con programadores de Java que están acostumbrados al término.

Acero silencioso
fuente
+1 para la distinción en la que ABC puede tener implementaciones propias, lo que parece una manera muy genial de
burlarse de
17

En general, las interfaces se usan solo en idiomas que usan el modelo de clase de herencia única. En estos lenguajes de herencia única, las interfaces se usan típicamente si alguna clase podría usar un método o conjunto de métodos en particular. También en estos lenguajes de herencia única, las clases abstractas se usan para tener variables de clase definidas además de ninguno o más métodos, o para explotar el modelo de herencia única para limitar el rango de clases que podrían usar un conjunto de métodos.

Los lenguajes que admiten el modelo de herencia múltiple tienden a usar solo clases o clases base abstractas y no interfaces. Dado que Python admite la herencia múltiple, no usa interfaces y querrás usar clases base o clases base abstractas.

http://docs.python.org/library/abc.html

Lara Dougan
fuente
2

Las clases abstractas son clases que contienen uno o más métodos abstractos. Junto con los métodos abstractos, las clases abstractas pueden tener métodos estáticos, de clase y de instancia. Pero en el caso de la interfaz, solo tendrá métodos abstractos, no otros. Por lo tanto, no es obligatorio heredar la clase abstracta, pero sí es obligatorio heredar la interfaz.

Akash Ukarande
fuente
1

Para completar, debemos mencionar PEP3119 donde se introdujo ABC y se comparó con las interfaces, y el Talin original comentario .

La clase abstracta no es la interfaz perfecta:

  • pertenece a la jerarquía de herencia
  • es mutable

Pero si considera escribirlo a su manera:

def some_function(self):
     raise NotImplementedError()

interface = type(
    'your_interface', (object,),
    {'extra_func': some_function,
     '__slots__': ['extra_func', ...]
     ...
     '__instancecheck__': your_instance_checker,
     '__subclasscheck__': your_subclass_checker
     ...
    }
)

ok, rather as a class
or as a metaclass
and fighting with python to achieve the immutable object
and doing refactoring
...

te darás cuenta rápidamente de que estás inventando la rueda para lograr abc.ABCMeta

abc.ABCMeta se propuso como una adición útil de la funcionalidad de la interfaz que falta, y eso es bastante justo en un lenguaje como python.

Ciertamente, se pudo mejorar mejor al escribir la versión 3, y agregar una nueva sintaxis y un concepto de interfaz inmutable ...

Conclusión:

The abc.ABCMeta IS "pythonic" interface in python
Sławomir Lenart
fuente