2011-06-25 11 views
21

Basado en mi comprensión de Python's data model, y específicamente en la subsección "Métodos de instancia", cada vez que lee un atributo cuyo valor es de tipo "función definida por el usuario", se activa algo mágico y se obtiene un método de instancia enlazado en lugar de la función real y original. Esa es la razón por la que no pasas explícitamente el parámetro self cuando llamas a un método.Asignando una función a un atributo de objeto

Pero entonces, yo esperaría a ser capaz de sustituir al método de un objeto con una función con la misma firma:

class Scriptable: 
    def __init__(self, script = None): 
     if script is not None: 
      self.script = script # replace the method 
    def script(self): 
     print("greetings from the default script") 

>>> scriptable = Scriptable() 
>>> scriptable.script() 
greetings from the default script 

>>> def my_script(self): 
...  print("greetings from my custom script") 
... 
>>> scriptable = Scriptable(my_script) 
>>> scriptable.script() 
Traceback (most recent call last): 
    ... 
TypeError: script() takes exactly 1 positional argument (0 given) 

estoy creando una instancia de Scriptable, y el establecimiento de su atributo script a un usuario función definida con un solo parámetro, tal como se define en la clase. Entonces, cuando leí el atributo scriptable.script, esperaría que la magia entrara en acción y me diera un método de instancia enlazado que no tomara ningún parámetro (como lo hago cuando no reemplacé script). En cambio, parece estar devolviendo exactamente la misma función que pasé, el parámetro self y todo. La magia vinculante al método no está sucediendo.

¿Por qué funciona la magia de vinculación de métodos cuando defino un método dentro de la declaración de clase, pero no cuando asigno el atributo? ¿Qué hace que Python trate estas situaciones de manera diferente?

Estoy usando Python3 si hace alguna diferencia.

+2

Al igual que con la mayoría de la magia, que fue diseñado para hacer exactamente lo que si me sale este derecho: "También es importante tener en cuenta que las funciones definidas por el usuario que son ** atributos de una instancia de clase ** son ** no ** convertidos a métodos enlazados, esto solo ocurre cuando la función es ** un atributo de la clase **. " Con la instrucción 'self.script' estás creando un atributo de instancia. El método estático 'Scriptable.script' todavía está allí. Una posible solución sería llamar a su 'script' inyectado en el antiguo método 'script'. –

Respuesta

13

Aquí es cómo hacerlo:

import types 
class Scriptable: 
    def __init__(self, script = None): 
     if script is not None: 
      self.script = types.MethodType(script, self) # replace the method 
    def script(self): 
     print("greetings from the default script") 

Como ba__friend se señala en los comentarios, los métodos se almacenan en el objeto class. Un descriptor en el objeto de clase devuelve funciones como métodos vinculados cuando accede al atributo desde una instancia.

Cuando asigna una función a instance no sucede nada especial, por lo que debe ajustar la función usted mismo.

+0

¡Genial! Después de leer el comentario de ba__friend, descubrí que podía hacer 'self.script = lambda: script (self)' para obtener los resultados deseados, pero eso está codificado para un método sin parámetros. La tuya es mucho mejor. –

+0

Mejor compruebe si 'script' se puede llamar solo para asegurarse – kentwait

6

vistazo a esto:

>>> scriptable = Scriptable() 
>>> scriptable.script 
<bound method Scriptable.script of <__main__.Scriptable instance at 0x01209DA0>> 
>>> scriptable = Scriptable(my_script) 
>>> scriptable.script 
<function my_script at 0x00CF9730> 

Declaración self.script = script crea sólo un atributo de un objeto de clase, sin ningún tipo de 'magia' con ella.

Declaración def script(self): dentro de una definición de clase crea un descriptor - objeto especial que realmente maneja todo lo relacionado con el parámetro self.

Puede leer más sobre los descriptores en Python en la referencia de modelo de datos mencionada: implementing-descriptors.

Un artículo más grande sobre los descriptores en Python de Raymond Hettinger: How-To Guide for Descriptors.

+0

Este es también un recorrido muy sencillo que hace exactamente lo que solicitó el OP: http://irrepupavel.com/documents/python/instancemethod/ (la mejor opción para crear "método enlazado de creación de python" en Google). –

+0

Hay magia similar, pero no creo que 'def script (self):' en realidad cree un descriptor. Si hago 'Scriptable.script = my_script', todo funciona como se espera, y no hay ningún descriptor creado allí. Parece que el comentario de ba__friend es correcto: la magia solo funciona para atributos en * tipo *, no atributos en * instancia *. –

+0

De acuerdo, estoy corregido. Después de jugar con el código de warvariuc (http://stackoverflow.com/questions/6478371/assigning-a-function-to-an-object-attribute/6479888#6479888), está claro que las funciones * son * descriptores: tienen automáticamente '__get__' atributos, aunque no veo eso documentado en ninguna parte. Pensé que 'tipo .__ getattr__' tenía un código de caso especial para las funciones, pero parece que en realidad las trata como cualquier otro descriptor. –

1

Realmente no puedo responder a su pregunta por qué funciona de esa manera, tendrá que pedir Guido van Rossum, pero puedo dar una posible solución:

class Scriptable: 
    def __init__(self, script = None): 
     self._script = script # replace the method 
    def script(self): 
     if self._script: return self._script(self) 
     return self._defaultscript() 
    def _defaultscript(self): 
     print("greetings from the default script") 
9

Gracias a Alex Martelli de answer aquí es otra versión:

class Scriptable: 
    def script(self): 
     print(self) 
     print("greetings from the default script") 

def another_script(self): 
    print(self) 
    print("greetings from the another script") 

s = Scriptable() 
s.script() 

# monkey patching: 
s.script = another_script.__get__(s, Scriptable) 
s.script() 
+3

Crazy. No tenía idea de que las funciones tenían automáticamente los atributos '__get__', la documentación ni siquiera menciona que sí lo hacen, pero al ejecutar su ejemplo, puedo ver que sí lo hacen. También lo hacen 'lambda's. Entonces, cada función puede usarse automáticamente como un descriptor ... Python continúa sorprendiéndome. Las cosas que pensé que eran especiales, resultaron ser generales. –

Cuestiones relacionadas