Java abstract / diseño de interfaz en Python


Tengo un número de clases que comparten los mismos métodos, solo que con diferentes implementaciones. En Java, tendría sentido que tiene cada una de estas clases implementan una interfaz o extender una clase abstracta. ¿Python tiene algo similar a esto, o debería estar tomando un enfoque alternativo?

Author: Matt, 2011-11-18

4 answers

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 necesitas: Python funciona según el principio EAFP (más fácil pedir perdón que permiso). Es decir, en lugar de especificar que acepta un objeto, no sé, ICloseable, simplemente intenta close el objeto cuando lo necesite, y si genera una excepción, entonces genera una excepción.

Así que en esta mentalidad solo escribirías tus clases por separado, y úsalos como quieras. 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, entonces simplemente funcionará, sin necesidad de especificar que implementa su interfaz particular.

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

El PEP que introduce el ABC explica mucho mejor de lo que puedo:

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

La invocación significa interactuar con un objeto invocando sus métodos. Por lo general, esto se combina con polimorfismo, de modo que la invocación de un dado el método puede ejecutar código diferente dependiendo del tipo de objeto.

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

Ambos patrones de uso tienen el mismo fin general, que es ser capaz de apoyar el procesamiento de objetos diversos y potencialmente novedosos en un de manera uniforme, pero al mismo tiempo permitiendo que las decisiones de procesamiento personalizado para cada tipo de objeto.

En la teoría clásica de OOP, la invocación es el patrón de uso preferido, y la inspección se desaconseja activamente, siendo considerada una reliquia de un anterior, estilo de programación procedimental. Sin embargo, en la práctica esta opinión es simplemente demasiado dogmático e inflexible, y conduce a una especie de diseño rigidez que está muy reñida 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 anticipado por el creador de la clase object. No lo es siempre la mejor solución para construir en cada objeto métodos que satisfacer las necesidades de cada posible usuario de ese objeto. Además, hay muchas poderosas filosofías de despacho que están en directa contraste con el requisito clásico de comportamiento OOP estrictamente encapsulado dentro de un objeto, siendo ejemplos regla o coincidencia de patrón lógica impulsada.

Por otro lado, una de las críticas a la inspección por parte de la OOP clásica teóricos es la falta de formalismos y la naturaleza ad hoc de lo que es siendo inspeccionado. En un lenguaje como Python, en el que casi cualquier aspecto de un objeto puede ser reflejado y accedido directamente por externos código, hay muchas maneras diferentes de probar si un objeto se ajusta a un protocolo en particular o no. Por ejemplo, si preguntar ' es esto objeto un contenedor de secuencia mutable?', uno puede buscar una clase base de 'list', o uno puede buscar un método llamado'getitem'. Pero nota que aunque estas pruebas pueden parecer obvias, ninguna de ellas es correcto, ya que uno genera falsos negativos, y el otro falso positivo.

El remedio generalmente acordado es estandarizar las pruebas, y agruparlos en un acuerdo formal. Esto se hace más fácilmente por asociación con cada clase un conjunto de propiedades estándar comprobables, ya sea a través del mecanismo de herencia o algún otro medio. Cada prueba lleva consigo un conjunto de promesas: contiene una promesa sobre la comportamiento general de la clase, y una promesa en cuanto a lo que otra clase los métodos estarán disponibles.

Este PEP propone una estrategia particular para organizar estas pruebas conocido como Clases Base Abstractas, o ABC. ABCs son simplemente clases de Python que se agregan al árbol de herencia de un objeto para señalar ciertos características 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, el ABCs define un conjunto mínimo de métodos que establecen el comportamiento característico del tipo. Código que discrimina los objetos basados en su tipo ABC pueden confiar en que esos métodos estar siempre presente. Cada uno de estos métodos va acompañado de un semántica abstracta generalizada definición que se describe en el documentación para el ABC. Estas definiciones semánticas estándar no son obligatorio, pero se recomienda encarecidamente.

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

 51
Author: Katriel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-18 11:42:03

No estoy tan familiarizado con Python, pero me atrevería a suponer que no lo está.

La razón por la que existen interfaces en Java es que especifican un contrato. Algo que implementa java.util.List, por ejemplo, está garantizado que tiene un método add() que se ajusta al comportamiento general definido 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.

Además, tanto el desarrollador como el compilador pueden saber que tal método existe y es invocable en el objeto en cuestión, incluso si no conocen su clase exacta. Es una forma de polimorfismo que se necesita con la tipificación estática 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á escrito estáticamente. No es necesario declarar la clase de un objeto, ni convencer al compilador que los métodos que estás usando definitivamente existen. Las "interfaces" en un mundo de duck-typing son tan simples como invocar el método y confiar en que el objeto puede manejar ese mensaje apropiadamente.

Nota-las ediciones de Pitonistas más conocedores son bienvenidas.

 4
Author: Andrzej Doyle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-18 11:30:52

Puede ser que puedas usar algo como esto. Esto actuará como una clase abstracta. Por lo tanto, cada subclase se ve obligada a implementar func1 ()

class Abstract:

    def func1(self):
        raise NotImplementedError("The method not implemented")
 3
Author: M S,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-18 11:34:06

Escribí una biblioteca en 3.5+ el permite escribir interfaces en Python.

Lo esencial es escribir un decorador de clases 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]))

Luego puedes escribir clases como esta:

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'
 1
Author: ksindi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-23 13:25:13