2009-02-23 7 views
5

En Python, he visto la recomendación de usar la celebración o el ajuste para extender la funcionalidad de un objeto o clase, en lugar de la herencia. En particular, creo que Alex Martelli habló sobre esto en su charla Python Design Patterns. He visto este patrón usado en bibliotecas para inyección de dependencia, como pycontainer.Cómo envolver objetos para ampliar/agregar funcionalidad mientras se trabaja alrededor de isinstance

Un problema que me he encontrado es que cuando tengo que interactuar con el código que utiliza el isinstance anti-pattern, este patrón falla porque el objeto de retención/envoltura no pasa la prueba isinstance. ¿Cómo puedo configurar el objeto de sujeción/envoltura para evitar la verificación innecesaria de tipos? ¿Se puede hacer esto genéricamente? En cierto sentido, necesito algo para las instancias de clase análogas a los decoradores de funciones que preservan la firma (por ejemplo, simple_decorator o decorator de Michele Simionato).

Calificación: No estoy afirmando que todo el uso de isinstance sea inapropiado; varias respuestas dan buenos puntos sobre esto. Dicho esto, debe reconocerse que el uso de isinstance presenta limitaciones significativas en las interacciones de objetos --- Obliga a herencia a ser la fuente de polimorfismo, en lugar de comportamiento.

Parece haber cierta confusión acerca de cómo/por qué esto es un problema, así que permítanme darles un ejemplo simple (ampliamente levantado de pycontainer). Digamos que tenemos una clase Foo, así como una FooFactory. Por el bien del ejemplo, supongamos que queremos ser capaces de instanciar objetos Foo que registran cada llamada a la función, o no ... piense en AOP. Además, queremos hacer esto sin modificar la clase/fuente Foo de ninguna manera (por ejemplo, podemos estar implementando una fábrica genérica que pueda agregar capacidad de registro a cualquier instancia de clase sobre la marcha). Un primer intento de esto podría ser:

class Foo(object): 
    def bar(): 
     print 'We\'re out of Red Leicester.' 

class LogWrapped(object): 
    def __init__(self, wrapped): 
     self.wrapped = wrapped 
    def __getattr__(self, name): 
     attr = getattr(self.wrapped, name) 
     if not callable(attr): 
      return attr 
     else: 
      def fun(*args, **kwargs): 
       print 'Calling ', name 
       attr(*args, **kwargs) 
       print 'Called ', name 
      return fun 

class FooFactory(object): 
    def get_foo(with_logging = False): 
     if not with_logging: 
      return Foo() 
     else: 
      return LogWrapped(Foo()) 

foo_fact = FooFactory() 
my_foo = foo_fact.get_foo(True) 
isinstance(my_foo, Foo) # False! 

Hay razones pueden por las que podría querer hacer las cosas exactamente de esta manera (uso decoradores lugar, etc.) pero tenga en cuenta:

  • Nos Don No quiero tocar la clase Foo. Supongamos que estamos escribiendo un código de marco que podría ser utilizado por clientes que aún no conocemos.
  • El punto es devolver un objeto que es esencialmente un Foo, pero con funcionalidad añadida. Debería parecer un Foo --- tanto como sea posible --- a cualquier otro código de cliente esperando un Foo. De ahí el deseo de trabajar alrededor de isinstance.
  • Sí, sé que no necesito la clase de fábrica (defendiéndome preemptivamente aquí).
+0

Agita mi puño en la furia impotente en isinstance. ¿No puedes arreglar el código con isinstance? http://stackoverflow.com/questions/423823/whats-your-favorite-programmer-ignorance-pet-peeve/423857#423857 –

+0

Furia desamparado :) En algunos casos, tengo acceso a isinstance. Pero otro caso de uso sería querer escribir el código "framework" (por ejemplo, pycontainer arriba) que funciona bien con otros. – zweiterlinde

Respuesta

2

Si el código de la biblioteca del que depende usa isinstance y depende de la herencia, ¿por qué no seguir esta ruta? Si no puede cambiar la biblioteca, probablemente sea mejor mantenerse coherente con ella.

También creo que hay usos legítimos para isinstance, y con la introducción de abstract base classes en 2.6 esto ha sido reconocido oficialmente. Hay situaciones en las que isinstance es realmente la solución correcta, en contraposición a la tipificación de pato con hasattr o mediante el uso de excepciones.

Algunas opciones sucias si por alguna razón usted realmente no desea utilizar la herencia:

  • podrá modificar sólo las instancias de la clase mediante el uso de métodos de instancia. Con new.instancemethod crea los métodos de contenedor para su instancia, que luego llama al método original definido en la clase original. Esta parece ser la única opción que no modifica la clase original ni define nuevas clases.

Si se puede modificar la clase en tiempo de ejecución hay muchas opciones:

  • Utilice un mixin tiempo de ejecución, es decir, sólo tiene que añadir una clase al atributo __base__ de su clase. Pero esto se usa más para agregar funcionalidad específica, no para envoltorios indiscriminados en los que no se sabe qué se debe envolver.

  • Las opciones en la respuesta de Dave (decoradores de clase en Python> = 2.6 o Metaclasses).

Editar: Para su ejemplo específico, supongo que solo funciona la primera opción. Pero aún consideraría la alternativa de crear un LogFoo o elegir una solución completamente diferente para algo específico como el registro.

0

Mi primer impulso sería la de tratar de solucionar el código erróneo que utiliza isinstance. De lo contrario, solo está propagando sus errores de diseño en su propio diseño. ¿Alguna razón por la que no puedes modificarla?

Editar: Por lo que su justificación es que estás escribiendo código marco/biblioteca que desea que la gente sea capaz de utilizar en todos los casos, incluso si quieren utilizar isinstance?

Creo que hay varias cosas mal con esto:

  • Usted está tratando de apoyar un paradigma roto
  • Usted es el que define la biblioteca y sus interfaces, le toca a los usuarios a utilizar es correcto
  • No hay manera de que posiblemente puede anticipar toda la mala programación de los usuarios de la biblioteca va a hacer, por lo que es más o menos un esfuerzo inútil tratar de apoyar a las malas prácticas de programación

Creo que es el mejor de la escritura idiomática, código bien diseñado. El código bueno (y el código incorrecto) tiende a propagarse, por lo que el suyo es un ejemplo. Es de esperar que conduzca a un aumento general en la calidad del código. Ir por el otro lado solo continuará la disminución de la calidad.

+0

La suposición es que la instancia siempre es una mala idea. Hay momentos válidos para usar isinstance. –

1

Una cosa a tener en cuenta es que no necesariamente tiene que usar cualquier cosa en una clase base si va por la ruta de herencia. Puede hacer que herede una clase de código auxiliar que no agrega ninguna implementación concreta.Yo he hecho algo como esto varias veces:

class Message(object): 
    pass 

class ClassToBeWrapped(object): 
    #... 

class MessageWithConcreteImplementation(Message): 
    def __init__(self): 
     self.x = ClassToBeWrapped() 
    #... add concrete implementation here 

x = MessageWithConcreteImplementation() 
isinstance(x, Message) 

Si necesita heredar de otras cosas, supongo que podría encontrarse con algunos problemas con la herencia múltiple, pero esto debería ser bastante mínimo si no se proporciona cualquier implementación concreta.

Un problema que me he encontrado es que cuando tengo que interactuar con el código que utiliza el anti-patrón isinstance

Estoy de acuerdo que isinstance debe ser evitada si es posible, pero me No estoy seguro de que lo llamaría antipatrón. Hay algunas razones válidas para usar isinstance. Por ejemplo, hay algunos marcos de paso de mensajes que usan esto para definir mensajes. Por ejemplo, si obtiene una clase que hereda de Shutdown, es hora de que se cierre el subsistema.

0

Si está escribiendo un marco que necesita aceptar algún tipo de entradas de los usuarios de su API, entonces no hay razón por la que se me ocurra usar isinstance. Feo como podría ser, siempre me acaba de comprobar para ver si realmente proporciona la interfaz me refiero a utilizar:

def foo(bar): 
    if hasattr(bar, "baz") and hasattr(bar, "quux"): 
     twiddle(bar.baz, bar.quux()) 
    elif hasattr(bar, "quuux"): 
     etc... 

Y también a menudo proporcionan una clase agradable para heredar la funcionalidad por defecto, si el usuario API quiere usar it:

class Bar: 
    def baz(self): 
     return self.quux("glu") 

    def quux(self): 
     raise NotImplemented 
Cuestiones relacionadas