2011-11-18 13 views
28

Tengo varias clases que comparten los mismos métodos, solo que con diferentes implementaciones. En Java, tendría sentido que cada una de estas clases implementara una interfaz o ampliara una clase abstracta. ¿Python tiene algo similar a esto, o debería tomar un enfoque alternativo?Java abstract/diseño de interfaz en Python

+0

Ver http://stackoverflow.com/questions/372042/difference -between-abstract-class-and-interface-in-python Tenga en cuenta el enlace al módulo de interfaz Zope como una implementación de interfaces tipo java. –

+0

¿hay una respuesta directa a esto hay interfaces o no? –

Respuesta

51

Hay un poco de historia detrás de las interfaces en Python. La actitud original, que prevaleció durante muchos años, es que no los necesita: Python funciona con el principio de EAFP (más fácil de pedir perdón que de permiso). Es decir, en lugar de especificar que acepta un objeto, I don, ICloseable, simplemente intente close el objeto cuando lo necesite, y si genera una excepción, se genera una excepción.

Así que en esta mentalidad solo escribirías tus clases por separado, y las usarías como quisieras. Si uno de ellos no cumple con los requisitos, su programa generará una excepción; Por el contrario, si escribe otra clase con los métodos correctos, simplemente funcionará, sin que necesite especificar que implementa su interfaz particular.

Esto funciona bastante bien, pero hay casos de uso definidos para las interfaces, especialmente con proyectos de software más grandes. La decisión final en Python fue proporcionar el módulo abc, que le permite escribir clases base abstractas, es decir, clases que no puede crear instancias a menos que anule todos sus métodos. Es su decisión si cree que usarlos vale la pena.

El PEP introducing ABCs explica mucho mejor que yo:

En el dominio de la programación orientada a objetos, los patrones de uso de en interacción con un objeto se pueden dividir en dos categorías básicas, que son 'invocación 'e' inspección '.

Invocación significa interactuar con un objeto invocando sus métodos. Normalmente esto se combina con polimorfismo, por lo que al invocar un método dado se puede ejecutar un código diferente según el tipo de objeto.

Inspección significa la capacidad de código externo (fuera de los métodos del objeto ) para examinar el tipo o propiedades de ese objeto, y tomar decisiones sobre cómo tratar a ese objeto basado en dicha información .

Ambos patrones de uso sirven para el mismo fin general, que es ser capaz de apoyo al tratamiento de diversos y potencialmente nuevos objetos de una manera uniforme , pero al mismo tiempo permitiendo que las decisiones de procesamiento para ser personalizado para cada diferente tipo de objeto

En la teoría clásica programación orientada a objetos, la invocación es el patrón de uso preferido, e inspección se desanima activamente, siendo considerado una reliquia de un anteriormente, el estilo de programación procedimental. Sin embargo, en la práctica, esta vista es simplemente demasiado dogmática e inflexible, y conduce a un tipo de rigidez de diseño que está muy en desacuerdo con la naturaleza dinámica de un lenguaje como Python.

En particular, a menudo hay una necesidad de procesar objetos de una manera que no fue anticipada por el creador de la clase de objeto. No es , siempre la mejor solución para incorporar a cada método de objeto que satisfaga las necesidades de cada usuario posible de ese objeto. Además, hay muchas filosofías de despacho potentes que están en contraste directo con al comportamiento OOP clásico de comportamiento siendo estrictamente encapsulado dentro de un objeto, ejemplos de reglas o lógica de coincidencia de patrones .

Por otra parte, una de las críticas de la inspección de programación orientada a objetos clásicos teóricos es la falta de formalismos y la naturaleza ad hoc de lo que está siendo inspeccionado . En un lenguaje como Python, en el que casi cualquier aspecto de de un objeto se puede reflejar y acceder directamente mediante el código externo , hay muchas formas diferentes de comprobar si un objeto cumple con un protocolo en particular o no. Por ejemplo, si al preguntar '¿es este objeto un contenedor de secuencia mutable?', Se puede buscar una clase base de 'list', o se puede buscar un método llamado 'getitem'. Pero tenga en cuenta que aunque estas pruebas pueden parecer obvias, ninguno de ellos es correcto, ya que uno genera falsos negativos, y el otro falso positivos.

El remedio generalmente acordado es estandarizar las pruebas, y agruparlas en un acuerdo formal. Esto se hace más fácilmente por asociando con cada clase un conjunto de propiedades comprobables estándar, , ya sea a través del mecanismo de herencia u otro medio. Cada prueba conlleva una serie de promesas: contiene una promesa sobre el comportamiento general de la clase y una promesa sobre qué otros métodos de clase estarán disponibles.

Este PEP propone una estrategia particular para organizar estas pruebas conocidas como Clases Base Abstractas, o ABC.Los ABC son simplemente clases Python que se agregan al árbol de herencia de un objeto para señalar ciertas características de de ese objeto a un inspector externo. Las pruebas se realizan usando isinstance(), y la presencia de un ABC particular significa que la prueba ha pasado.

Además, los ABC definen un conjunto mínimo de métodos que establecen el comportamiento característico del tipo. El código que discrimina objetos según su tipo ABC puede confiar en que esos métodos siempre estarán presentes . Cada uno de estos métodos está acompañado por una definición semántica abstracta generalizada que se describe en la documentación para el ABC. Estas definiciones semánticas estándar no son aplicadas, pero se recomiendan encarecidamente.

Al igual que todas las demás cosas en Python, estas promesas están en la naturaleza de acuerdo a de caballeros, que en este caso significa que mientras el lenguaje hace cumplir algunas de las promesas hechas en el ABC, es hasta a la implementador de la clase concreta para asegurar que se guarden los restantes.

+0

vamos, esto es demasiado largo, ¿hay interfaces o no? –

+0

Sí, y se llaman Clases base abstractas – Matt

3

Puede ser que pueda utilizar algo como esto. Esto actuará como una clase abstracta. Cada subclase se ve forzado a poner en práctica func1()

class Abstract: 

    def func1(self): 
     raise NotImplementedError("The method not implemented") 
+0

Esto ya está en stdlib como 'abc' (http://docs.python.org/library/abc.html). – katrielalex

+1

Bueno, 'abc' es mucho mejor que el ejemplo anterior, porque' abc' generará un error cuando se crea la clase, mientras que el ejemplo anterior solo aparece cuando se llama al método. – madjar

4

No estoy tan familiarizado con Python, pero me gustaría aventurar una respuesta que no es así.

La razón por la que existen interfaces en Java es que especifican un contrato . Algo que implementa java.util.List, por ejemplo, garantiza tener un método add() para cumplir con el comportamiento general tal como se define en la interfaz. Podría incluir cualquier implementación (sana) de List sin conocer su clase específica, llamar a una secuencia de métodos definidos en la interfaz y obtener el mismo comportamiento general.

Además, tanto el desarrollador como el compilador pueden saber que dicho método existe y que se puede llamar al objeto en cuestión, incluso si no conocen su clase exacta. Es una forma de polimorfismo que se necesita con el tipado estático para permitir diferentes clases de implementación y aún así saber que todas son legales.

Esto realmente no tiene sentido en Python, porque no está tipado estáticamente. No es necesario declarar la clase de un objeto ni convencer al compilador de que existen métodos definitivamente. Las "interfaces" en un mundo de tipa de patos son tan simples como invocar el método y confiar en que el objeto puede manejar ese mensaje de manera apropiada.

Nota: las ediciones de Pythonistas más conocedores son bienvenidas.

+0

O porque no hay herencia múltiple en Java. –

0

Escribí library en 3.5+ el permite escribir interfaces en Python.

Lo esencial es escribir un decorador de clase con la ayuda de inspect.

import inspect 


def implements(interface_cls): 
    def _decorator(cls): 
     verify_methods(interface_cls, cls) 
     verify_properties(interface_cls, cls) 
     verify_attributes(interface_cls, cls) 
     return cls 

    return _decorator 


def verify_methods(interface_cls, cls): 
    methods_predicate = lambda m: inspect.isfunction(m) or inspect.ismethod(m) 
    for name, method in inspect.getmembers(interface_cls, methods_predicate): 
     signature = inspect.signature(method) 
     cls_method = getattr(cls, name, None) 
     cls_signature = inspect.signature(cls_method) if cls_method else None 
     if cls_signature != signature: 
      raise NotImplementedError(
       "'{}' must implement method '{}({})' defined in interface '{}'" 
       .format(cls.__name__, name, signature, interface_cls.__name__) 
      ) 


def verify_properties(interface_cls, cls): 
    prop_attrs = dict(fget='getter', fset='setter', fdel='deleter') 
    for name, prop in inspect.getmembers(interface_cls, inspect.isdatadescriptor): 
     cls_prop = getattr(cls, name, None) 
     for attr in prop_attrs: 
      # instanceof doesn't work for class function comparison 
      if type(getattr(prop, attr, None)) != type(getattr(cls_prop, attr, None)): 
       raise NotImplementedError(
        "'{}' must implement a {} for property '{}' defined in interface '{}'" # flake8: noqa 
        .format(cls.__name__, prop_attrs[attr], name, interface_cls.__name__) 
       ) 


def verify_attributes(interface_cls, cls): 
    interface_attributes = get_attributes(interface_cls) 
    cls_attributes = get_attributes(cls) 
    for missing_attr in (interface_attributes - cls_attributes): 
     raise NotImplementedError(
      "'{}' must have class attribute '{}' defined in interface '{}'" 
      .format(cls.__name__, missing_attr, interface_cls.__name__) 
     ) 


def get_attributes(cls): 
    boring = dir(type('dummy', (object,), {})) 
    return set(item[0] for item in inspect.getmembers(cls) 
       if item[0] not in boring and not callable(item[1])) 

entonces usted puede escribir clases como esto:

class Quackable: 
    def quack(self) -> bool: 
     pass 


@implements(Quackable) 
class MallardDuck:  
    def quack(self) -> bool: 
     pass 

A continuación le daría un error sin embargo:

@implements(Quackable) 
class RubberDuck:  
    def quack(self) -> str: 
     pass 

NotImplementedError: 'RubberdDuck' must implement method 'quack((self) -> bool)' defined in interface 'Quackable'