2012-01-04 6 views
7

Tengo una clase en el proyecto principal que no quiero cambiar.¿Cómo llamar al método original cuando está parchado?

class A(): 
    def __init__(self, firstname, lastname): 
     self.firstname = firstname 
     self.lastname = lastname 

    def name(self): 
     # this method could be much more complex 
     return self.lastname.upper() 

Estoy intentando construir un plugin mechansim. Hasta aquí todo bien, tengo un punto de extensión de esta manera:

if __name__ == '__main__': 
    ''' The main project has an extension point that allows me to do''' 
    # for each class extension such as AExtended: 
    A.name = AExtended.name 
    ''' After the extensions are loaded, some behaviours may be changed''' 
    a = A("John", "Doe") 
    print(a.name()) 

Un plugin se puede escribir así:

class AExtended(A): 
    ''' This is an extension I provide through a plugin mechanism 
    ''' 
    def name(self): 
     return self.firstname + ' ' + self.lastname.upper() 

Todo esto funciona muy bien. Ahora obtengo "John DOE".

Mi problema es que el método original name() puede ser bastante complejo. En otras palabras, no puedo darme el lujo de llamar al self.lastname.upper() en el AExtended. Me gustaría llamar al método "super", que ya no existe, porque ha sido sobrescrito.

¿Cómo puedo cambiar mi código, a fin de lograr algo como esto:

class AExtended(A): 
    def name(self): 
     # I'm looking for a way to call the base implementation that was in A.name() 
     return self.firstname + ' ' + parent.name() 

Gracias por su ayuda!

Editar: Algunas explicaciones de lo que intento hacer.

  • Quiero que el complemento parchee el comportamiento de A. No me puedo permitir a los consumidores cambiar existentes de un
  • Hay muchas clases como A que se podrían cambiar, me gustaría plugins para tener un control total y la responsabilidad
  • Es cierto AExtended no tiene que heredar de A, pero fue una manera fácil de acceder al self.firstname. No tengo problemas para seguir un patrón de diseño diferente si puede ayudar.

tengo una solución, pero no es muy elegante y difícil generalizar

class AExtended(A): 
    def name(self): 
     # I'm looking for a way to call the base implementation that was in A.name() 
     return self.firstname + ' ' + self.parentname() 
#in main 
A.parentname = A.name 
A.name = AExtended.name 
+0

Sí, leí cómo [llamar al método de la clase padre] (http://stackoverflow.com/questions/805066/how-to-call-a-parent-classs-method-from- child-class-in-python) pero en 'AExtended.name',' self' es un 'A' debido a la anulación – rds

+0

Podría ser más linco con [anulando un método privado] (http://stackoverflow.com/ preguntas/2484215/how-do-i-override-a-parent-classs-functions-in-python) pero no puedo cambiar el nombre del método A. – rds

+0

Dadas sus aclaraciones, le sugiero que cambie el título de la pregunta. Un título más preciso podría ser "¿Cómo llamar al método original cuando está parchado?" – Weeble

Respuesta

3

Aquí hay un ejemplo completo de lo que estaba insinuando. Siéntase libre de gritarme y hacerme fusionar mis respuestas, o rechazar una o más, simplemente es más fácil ofrecer una alternativa como una nueva respuesta. Dejaré que el código hable en lugar de explicarlo mal.:)

## Some shared class that is used all over the place and needs to be patched. 

class A(object): 
    def __init__(self): 
     self.firstname = 'Bob' 

    # Print my first name. 
    def name(self): 
     return self.firstname 

    # Use this to allow patching arbitrary methods... 
    @classmethod 
    def patch(cls, func_name): 
     def patch_by_name(new_func): 
      old_func = getattr(cls, func_name) 
      def patched_func(self): 
       return new_func(self, old_func) 
      setattr(cls, func_name, patched_func) 
     return patch_by_name 

## Some other area of the code where you want to throw in a patch 

class PatchedA(A): # doesn't need to subclass, but comes in handy sometimes 
    @A.patch('name') 
    def name(self, orig_func): 
     return 'I am ' + orig_func(self) + 'McWizwaz' 

print 'Who are you, A class?' 
print A().name() # prints 'I am Bob McWizwaz' 
+0

(Y por supuesto, puede convertirlo no en un método de clase y solo en una función independiente y llamarlo '@patch (A, 'name')' en su lugar, por lo que es utilizable para cualquier clase en cualquier lugar). – Spencer

+0

No lo haría ¿No será más fácil poner ese decorador '@ patch' fuera de' A' y también aplicarlo al parche 'def name'_outside_ any subclass? ¿Cuál es exactamente el propósito de una subclase 'PatchedA' de todos modos, cuando reemplazas el método original de 'A'? –

0

Una opción que no siempre podría ser el mejor en un lenguaje como Python es usar el decorador @override de this non-standard package. Pero esta es una opción viable solo si sus dos funciones funcionan en diferentes tipos o diferentes números de argumentos. Aparte de eso, no hay mucho que puedas hacer, además de cambiar el nombre de tu función.

+0

Paquete interesante, pero no veo cómo me puede ayudar: -/ – rds

1
class ABase(object): 
    def name(self): 
     pass 

class A(object): 
    pass 

class AExtension(ABase): 
    def name(self): 
     return ABase.name(self) 

A.name = AExtension.name 
+0

No estoy seguro de entender qué clase debe usarse. Necesito copiar todos los métodos de 'ABase' a' A' ('A.name = ABase.name' etc) y luego cargar extensiones (' A.name = AExtension.name')? – rds

+0

'A' ya tiene todos los métodos de' ABase' porque 'A' se extiende' ABase'. Pero en general, la idea de reasignar métodos a otras clases parece sospechosa. Puede encontrar algunas ideas sobre cómo crear correctamente una arquitectura de complementos mínima en este tema: http://stackoverflow.com/questions/932069/building-a-minimal-plugin-architecture-in-python – Ski

+0

@Skirmantas, no es raro decorar funciones como él quiere hacer. Las versiones más nuevas del lenguaje incluso admiten azúcar sintáctico para hacer que mi ejemplo sea aún más fácil de escribir. – Spencer

12

Esto es lo que llamamos un patrón de 'decorador'. Reemplace la reasignación original del nombre para que llame a una función, que toma el original. Luego devuelve una nueva función.

def name_decorator(method): 
    def decorate_name(self=None): 
     return stuff + method(self) 
    return decorate_name 
A.name = name_decorator(A.name) 

Más tarde, llamando A.name llamará decorate_name con self como la instancia actual y method estará disponible a lo que apunta a la función en el momento de la reasignación.

+0

+1 porque los decoradores de pitón están haciendo exactamente lo que quiero. Aceptaré esta respuesta cuando pueda verificar que se puede generalizar. – rds

+0

Uso Python 3. ¿Qué sintaxis puede ayudar el azúcar? – rds

+0

Consulte: http://docs.python.org/reference/compound%5Fstmts.html#function: en lugar de realizar anulaciones explícitas, puede colocar '@ my_decorator' justo antes de cualquier definición de función que desee decorar. – Spencer

Cuestiones relacionadas