¿Cómo implemento interfaces en Python?

182
public interface IInterface
{
    void show();
}

 public class MyClass : IInterface
{

    #region IInterface Members

    public void show()
    {
        Console.WriteLine("Hello World!");
    }

    #endregion
}

¿Cómo implemento Python equivalente a este código C #?

class IInterface(object):
    def __init__(self):
        pass

    def show(self):
        raise Exception("NotImplementedException")


class MyClass(IInterface):
   def __init__(self):
       IInterface.__init__(self)

   def show(self):
       print 'Hello World!'

¿¿Es esta una buena idea?? Por favor, da ejemplos en tus respuestas.

Pratik Deoghare
fuente
¿Cuál sería el propósito de usar una interfaz en su caso?
Bandi-T el
23
Francamente, no tiene ningún propósito! Solo quiero saber qué hacer cuando necesites interfaces en python.
Pratik Deoghare el
18
raise NotImplementedErrores lo showque debería ser el cuerpo, ¡no tiene sentido criar un genérico completamente Exceptioncuando Python define uno incorporado perfectamente específico! -)
Alex Martelli,
2
¿No debería init llamar a show () en IInterface (o aumentar la excepción en sí) para que no pueda crear una instancia de una interfaz abstracta?
Katastic Voyage
1
Puedo ver algún uso para esto ... digamos que tiene un objeto que desea asegurar que tenga una firma específica. Con la escritura de pato no puede garantizar que el objeto sea de la firma que espera. A veces puede ser útil aplicar algo de escritura en propiedades de escritura dinámica.
Prime By Design

Respuestas:

151

Según lo mencionado por otro aquí:

Las interfaces no son necesarias en Python. Esto se debe a que Python tiene una herencia múltiple adecuada, y también ducktyping, lo que significa que los lugares donde debe tener interfaces en Java, no tiene que tenerlas en Python.

Dicho esto, todavía hay varios usos para las interfaces. Algunos de ellos están cubiertos por Pythons Abstract Base Classes, introducidos en Python 2.6. Son útiles si desea crear clases base que no se puedan instanciar, pero que proporcionen una interfaz específica o parte de una implementación.

Otro uso es si de alguna manera desea especificar que un objeto implementa una interfaz específica, y también puede usar ABC para eso subclasificando de ellos. Otra forma es zope.interface, un módulo que forma parte de Zope Component Architecture, un marco de componentes realmente genial. Aquí no se subclasifican las interfaces, sino que se marcan las clases (o incluso las instancias) como implementando una interfaz. Esto también se puede utilizar para buscar componentes desde un registro de componentes. ¡Super guay!

Lennart Regebro
fuente
11
¿Podrías dar más detalles sobre esto? 1. ¿Cómo se implementa una interfaz de este tipo? 2. ¿Cómo se puede usar para buscar componentes?
geodésico
43
"Las interfaces no son necesarias en Python. Excepto cuando lo son".
Baptiste Candellier
8
Las interfaces se utilizan principalmente para tener un resultado predecible / imponer la corrección de los miembros al pasar objetos. Sería genial si Python apoyara esto como una opción. también permitiría que las herramientas de desarrollo tengan una mejor inteligencia
Sonic Soul
1
Un ejemplo mejoraría enormemente esta respuesta.
bob
55
"Esto se debe a que Python tiene una herencia múltiple adecuada", ¿quién dijo que las interfaces son para herencia múltiple?
adnanmuttaleb
76

El uso del módulo abc para clases base abstractas parece ser el truco.

from abc import ABCMeta, abstractmethod

class IInterface:
    __metaclass__ = ABCMeta

    @classmethod
    def version(self): return "1.0"
    @abstractmethod
    def show(self): raise NotImplementedError

class MyServer(IInterface):
    def show(self):
        print 'Hello, World 2!'

class MyBadServer(object):
    def show(self):
        print 'Damn you, world!'


class MyClient(object):

    def __init__(self, server):
        if not isinstance(server, IInterface): raise Exception('Bad interface')
        if not IInterface.version() == '1.0': raise Exception('Bad revision')

        self._server = server


    def client_show(self):
        self._server.show()


# This call will fail with an exception
try:
    x = MyClient(MyBadServer)
except Exception as exc:
    print 'Failed as it should!'

# This will pass with glory
MyClient(MyServer()).client_show()
Peter Torpman
fuente
11
Yugghh, que necesita algún módulo para lo que debería ser parte del lenguaje en sí, o no debe usarse en absoluto, IMO.
Mike de Klerk
Qué quiere decir: if not server.version() == '1.0': raise ...? Realmente no entiendo esta línea. Una explicación sería bienvenida.
Skandix
1
@MikedeKlerk No podría estar más de acuerdo. Al igual que la respuesta de Python a escribir; No debería tener que importar un módulo para declarar que quiero que un tipo sea un tipo. La respuesta a eso suele ser "bueno, Python se escribe dinámicamente", pero eso no es una excusa. Java + Groovy resuelve este problema. Java para las cosas estáticas, Groovy para las cosas dinámicas.
ubiquibacon
66
@MikedeKlerk, el módulo abc está integrado en python. Es un poco más de trabajo configurar algunos de estos patrones porque son en gran medida innecesarios en Python debido a patrones alternativos que se consideran 'más pitónicos'. Para la gran mayoría de los desarrolladores, sería como usted dijo, "no se usa en absoluto". Sin embargo, reconociendo que hay algunos casos muy específicos que realmente requieren estas capacidades de interfaz, los creadores de Python proporcionaron una API fácil de usar para hacerlo posible.
David Culbreth el
39

La interfaz es compatible con Python 2.7 y Python 3.4+.

Para instalar la interfaz tienes que

pip install python-interface

Código de ejemplo:

from interface import implements, Interface

class MyInterface(Interface):

    def method1(self, x):
        pass

    def method2(self, x, y):
        pass


class MyClass(implements(MyInterface)):

    def method1(self, x):
        return x * 2

    def method2(self, x, y):
        return x + y
rayo azul
fuente
77
Una de las principales ventajas de esta biblioteca, en mi humilde opinión, es la falla temprana que le brinda: si su clase no implementa correctamente una interfaz específica, obtendrá una excepción tan pronto como se lea la clase, ni siquiera tiene que usarla . Con la propia clase base abstracta de Python, obtienes la excepción cuando creas una instancia de tu clase por primera vez, lo que podría ser mucho más tarde.
Hans
Es innecesario, ABC ofrece una funcionalidad incorporada similar.
Daniel
@DanielCasares ¿ABC ofrece una interfaz real o quiere decir que las clases abstractas sin estado o implementaciones son la solución que ofrece ABC?
asaf92
36

Implementar interfaces con clases base abstractas es mucho más simple en Python 3 moderno y sirven como un contrato de interfaz para extensiones de plug-in.

Cree la interfaz / clase base abstracta:

from abc import ABC, abstractmethod

class AccountingSystem(ABC):

    @abstractmethod
    def create_purchase_invoice(self, purchase):
        pass

    @abstractmethod
    def create_sale_invoice(self, sale):
        log.debug('Creating sale invoice', sale)

Cree una subclase normal y anule todos los métodos abstractos:

class GizmoAccountingSystem(AccountingSystem):

    def create_purchase_invoice(self, purchase):
        submit_to_gizmo_purchase_service(purchase)

    def create_sale_invoice(self, sale):
        super().create_sale_invoice(sale)
        submit_to_gizmo_sale_service(sale)

Opcionalmente, puede tener una implementación común en los métodos abstractos como en create_sale_invoice(), llamándolo super()explícitamente en la subclase como anteriormente.

La creación de instancias de una subclase que no implementa todos los métodos abstractos falla:

class IncompleteAccountingSystem(AccountingSystem):
    pass

>>> accounting = IncompleteAccountingSystem()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class IncompleteAccountingSystem with abstract methods
create_purchase_invoice, create_sale_invoice

También puede tener propiedades abstractas, métodos estáticos y de clase combinando las anotaciones correspondientes con @abstractmethod.

Las clases base abstractas son excelentes para implementar sistemas basados ​​en complementos. Se puede acceder a todas las subclases importadas de una clase __subclasses__(), por lo que si carga todas las clases desde un directorio de complementos importlib.import_module()y si subclasan la clase base, tiene acceso directo a ellas a través de __subclasses__()y puede estar seguro de que el contrato de interfaz se aplica para todos ellos durante la instanciación.

Aquí está la implementación de carga de complementos para el AccountingSystemejemplo anterior:

...
from importlib import import_module

class AccountingSystem(ABC):

    ...
    _instance = None

    @classmethod
    def instance(cls):
        if not cls._instance:
            module_name = settings.ACCOUNTING_SYSTEM_MODULE_NAME
            import_module(module_name)
            subclasses = cls.__subclasses__()
            if len(subclasses) > 1:
                raise InvalidAccountingSystemError('More than one '
                        f'accounting module: {subclasses}')
            if not subclasses or module_name not in str(subclasses[0]):
                raise InvalidAccountingSystemError('Accounting module '
                        f'{module_name} does not exist or does not '
                        'subclass AccountingSystem')
            cls._instance = subclasses[0]()
        return cls._instance

Luego puede acceder al objeto de complemento del sistema de contabilidad a través de la AccountingSystemclase:

>>> accountingsystem = AccountingSystem.instance()

(Inspirado en esta publicación de PyMOTW-3 ).

mrts
fuente
Pregunta: ¿Qué significa el nombre del módulo "ABC"?
Sebastian Nielsen
"ABC" significa "Clases base abstractas", ver documentos oficiales
mrts
31

Hay implementaciones de interfaces de terceros para Python (la más popular es Zope , también utilizada en Twisted ), pero más comúnmente los codificadores de Python prefieren usar el concepto más rico conocido como "Clase base abstracta" (ABC), que combina una interfaz con la posibilidad de tener algunos aspectos de implementación allí también. ABC está particularmente bien soportado en Python 2.6 y versiones posteriores, vea el PEP , pero incluso en versiones anteriores de Python normalmente se los ve como "el camino a seguir" - solo defina una clase cuyos métodos generen NotImplementedErrorpara que las subclases sean ¡Aviso que es mejor que anulen esos métodos! -)

Alex Martelli
fuente
3
Hay implementaciones de interfaces de terceros para Python ¿Qué significa? ¿Podría por favor explicar ABC?
Pratik Deoghare el
2
Bueno, voy a estar en desacuerdo con que ABC sea "más rico". ;) Hay cosas que puede hacer zope.interface que los ABC no pueden hacer al revés. Pero por lo demás, como siempre, tienes razón. +1
Lennart Regebro
1
@Alfred: Significa que módulos como zope.interface no están incluidos en la biblioteca estándar, pero están disponibles en pypi.
Lennart Regebro el
Todavía tengo dificultades para asimilar el concepto de ABC. ¿Sería posible que alguien reescribiera twistedmatrix.com/documents/current/core/howto/components.html (en mi humilde opinión, una excelente explicación del concepto de interfaces) en términos de ABC. ¿Tiene sentido?
mcepl
21

Algo como esto (podría no funcionar ya que no tengo Python):

class IInterface:
    def show(self): raise NotImplementedError

class MyClass(IInterface):
    def show(self): print "Hello World!"
Bandido
fuente
2
¿Qué debo hacer con __init__(self)el constructor?
Pratik Deoghare el
1
Depende de usted. Como no hay una verificación en tiempo de compilación contra la construcción de un objeto a partir de una clase abstracta, no obtendría ninguna protección durante la codificación / compilación. Habrá un constructor heredado, por lo que el objeto se creará, solo estará "vacío". Depende de usted decidir si sería mejor permitir que esto suceda y detectar fallas más tarde, o detener explícitamente el programa en ese mismo momento implementando un constructor similar que arroje una excepción.
Bandi-T el
1
Es por eso que abc.ABCes mucho mejor que aumentar NotImplementedError: la creación de instancias de una abc.ABCsubclase que no implementa todos los métodos abstractos falla temprano, por lo que está protegido contra errores. Vea mi respuesta a continuación para ver cómo se ve el error.
mrts
8

Tengo entendido que las interfaces no son tan necesarias en lenguajes dinámicos como Python. En Java (o C ++ con su clase base abstracta), las interfaces son medios para garantizar que, por ejemplo, esté pasando el parámetro correcto, capaz de realizar un conjunto de tareas.

Por ejemplo, si tiene observador y observable, observable está interesado en suscribir objetos que admitan la interfaz IObserver, que a su vez tiene notifyacción. Esto se verifica en tiempo de compilación.

En Python, no existe tal cosa compile timey las búsquedas de métodos se realizan en tiempo de ejecución. Además, uno puede anular la búsqueda con los métodos mágicos __getattr __ () o __getattribute __ (). En otras palabras, puede pasar, como observador, cualquier objeto que pueda devolver invocable al acceder al notifyatributo.

Esto me lleva a la conclusión de que las interfaces en Python existen : es solo que su aplicación se pospone hasta el momento en que se usan realmente

Tomasz Zieliński
fuente