2009-10-14 6 views
6

Necesito escribir una clase que permita que una subclase establezca un atributo con el nombre de una función. Esa función debe poder llamarse desde instancias de la clase.¿Escribir una clase que acepte una devolución de llamada en Python?

Por ejemplo, digo que necesito escribir una clase de frutas donde la subclase puede pasar en un mensaje de bienvenida. La clase Fruit debe exponer un atributo print_callback que se puede establecer.

class Fruit(object): 
    print_callback = None 

    def __init__(self, *args, **kwargs): 
     super(Fruit, self).__init__(*args, **kwargs) 
     self.print_callback("Message from Fruit: ") 

necesito para exponer una API que se puede ser consumido por el código (para ser claros, este código no puede cambiar, dicen que es el código de 3 ª parte):

def apple_print(f): 
    print "%sI am an Apple!" % f 

class Apple(Fruit): 
    print_callback = apple_print 

Si me quedo:

mac = Apple() 

quiero llegar:

Message from Fruit: I am an Apple!

En vez me sale:

TypeError: apple_print() takes exactly 1 argument (2 given)

Creo que esto se debe a que uno mismo se pasa como primer argumento.

Entonces, ¿cómo escribo la clase Fruit? ¡Gracias!

+1

aprecio los problemas que ha tenido con este enfoque y las excelentes soluciones a continuación. Sin embargo, esto me parece que debería definir 'print_callback' como un método (tal vez simplemente llamado' print_me', y obtener subclases para anularlo.Eso sería simple y pitónico. Para cada tipo diferente de método 'print_me', crea una subclase diferente. –

+0

Mi motivación para plantear la pregunta fue que quería subclasificar google.appengine.ext.db.djangoforms de Google App Engine (por ejemplo, como DF2) para permitir la anulación de un atributo formfield_callback de la misma manera que ModelForm de Django (django.forms.ModelForm) La idea es que la 'clase f (DF2)' podría ser utilizada por el estándar Django simplemente cambiando la superclase a django.forms.ModelForm. No sé por qué los autores de Django definieron formfield_callback como un atributo. –

Respuesta

7

Python supone que las funciones vinculadas dentro de un alcance de clase son métodos. Si desea tratarlos como funciones, hay que cavar alrededor de sus atributos para recuperar la función de objeto original:

def __init__(self, *args, **kwargs): 
    super(Fruit, self).__init__(*args, **kwargs) 

    # The attribute name was changed in Python 3; pick whichever line matches 
    # your Python version. 
    callback = self.print_callback.im_func # Python 2 
    callback = self.print_callback.__func__ # Python 3 

    callback("Message from Fruit: ") 
+0

O mejor aún, solo agrega un argumento 'self' a la función para que pueda actuar como un método normal. –

+0

Según la pregunta, la definición de devolución de llamada no se puede cambiar. –

+1

Por cierto, también vale la pena señalar que el atributo 'im_func' de los métodos se renombró a' __func__' en Python 3.x. –

2

Actualizado: la incorporación de abourget's suggestion utilizar staticmethod:

intente esto:

def __init__(self, *args, **kwargs): 
    super(Fruit, self).__init__(*args, **kwargs) 

    # Wrap function back into a proper static method 
    self.print_callback = staticmethod(self.print_callback) 

    # And now you can do: 
    self.print_callback("Message from Fruit: ") 
3

Se puede utilizar directamente:

class Apple(Fruit): 
    print_callback = staticmethod(apple_print) 

o:

class Apple(Fruit): 
    print_callback = classmethod(apple_print) 

En el primer caso, obtendrá un solo parámetro (el original). En el segundo, recibirás dos parámetros donde el primero será la clase a la que se llamó.

Espero que esto ayude, y es más corto y menos complejo.

+1

"para ser claros, este código no puede cambiar, digamos que es un código de terceros" –

0

También hay una solución dirtyer bits con metaclases:

def apple_print(f): 
    print "Apple " + f 

class FruitMeta(type): 
    def __new__(cls, name, bases, dct): 
     func = dct["print_callback"] 
     dct["print_callback"]=lambda x,f,func=func: func(f) 
     return type.__new__(cls,name,bases,dct) 

class Fruit(object): 
    __metaclass__ = FruitMeta 
    print_callback = None 
    def __init__(self): 
     super(Fruit,self).__init__() 
     self.print_callback("Msg ") 

class Apple(Fruit): 
    print_callback = apple_print 

mac = Apple()here 

manipula la clase antes de su creación!

1

que estaba buscando algo más parecido a esto cuando me encontré con esta pregunta:

class Something: 
    def my_callback(self, arg_a): 
     print arg_a 

class SomethingElse: 
    def __init__(self, callback): 
     self.callback = callback 

something = Something() 
something_else = SomethingElse(something.my_callback) 
something_else.callback("It works...") 
Cuestiones relacionadas