2012-05-24 16 views
16

Considere una función simple comoconvertir el tipo de función incorporada al tipo de método (en Python 3)

def increment(self): 
    self.count += 1 

que se ejecuta a través de Cython y compilado en un módulo de extensión. Supongamos ahora que me gustaría hacer de esta función un método en una clase. Por ejemplo:

class Counter: 
    def __init__(self): 
     self.count = 0 

from compiled_extension import increment 
Counter.increment = increment 

Ahora esto no funcionará, ya que la convención de llamadas en el nivel C se romperá. Por ejemplo:

>>> c = Counter() 
>>> c.increment() 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: increment() takes exactly one argument (0 given) 

Pero en Python 2, podemos convertir la función de un método no unido haciendo:

Counter.increment = types.MethodType(increment, None, Counter) 

¿Cómo puedo lograr esto mismo en Python 3?

Una forma sencilla es utilizar un envoltorio delgado:

from functools import wraps 
def method_wraper(f): 
    def wrapper(*args, **kwargs): 
     return f(*args, **kwargs) 
    return wraps(f)(wrapper) 

Counter.increment = method_wrapper(increment) 

¿Hay una manera más eficiente de hacerlo?

+2

he tenido este problema tratando de construir una clase Heapq utilizando el módulo 'heapq' feo. Tu solución es buena Se puede hacer en una línea, pero tiene la misma eficacia: 'def method_wraper (f): return functools.wraps (f) (lambda * a, ** kw: f (* a, ** kw))' – JBernardo

+0

. ..Interesante, la asignación parece funcionar bien si la función se define dentro del mismo módulo (método independiente que se asigna a una clase y vinculado a creación de instancias). Entonces, ¿es solo un problema con las extensiones C o con funciones en diferentes módulos? De todos modos, es posible que desee comprobar http://stackoverflow.com/questions/7490879/python3-bind-method-to-class-instance-with-get-it-works-but-why, lo que podría ayudar un poco también. – JAB

+0

Además, http://docs.python.org/py3k/howto/descriptor.html#functions-and-methods "la implementación C real de PyMethod_Type en Objects/classobject.c es un solo objeto con dos representaciones diferentes según si el campo im_self está establecido o es NULL (el equivalente en C de None). " Lo que hace que parezca que este problema no debería estar ocurriendo en absoluto, a menos que Python de alguna manera no actualice ese campo directamente para los métodos de un objeto al crear una instancia del objeto. – JAB

Respuesta

0

Importe la extensión de la siguiente manera:

import compiled_extension 

en su clase en la que escribir:

def increment: return compiled_extension.increment() 

Esto parece más legible y puede ser más eficiente.

4

Lo primero es conseguir los nombres correctamente:

>>> def increment(obj): 
...  obj.count += 1 
... 
>>> class A(object): 
...  def __init__(self): 
...   self.count = 0 
... 
>>> o = A() 
>>> o.__init__ 
<bound method A.__init__ of <__main__.A object at 0x0000000002766EF0>> 
>>> increment 
<function increment at 0x00000000027797C8> 

nombres propios son Así funciones y Métodos vinculados. Ahora se puede buscar la manera de Bind an Unbound Method y es probable que el resultado final será la lectura sobre descriptors:

En general, un descriptor es un atributo de objeto con " comportamiento de enlace", uno cuyo acceso atributo se ha reemplazado por métodos en el protocolo descriptor Esos métodos son __get__, __set__ y __delete__. Si alguno de esos métodos está definido para un objeto, se dice que es un descriptor.

Puede transformar fácilmente la función de método simplemente usando diferentes invocación de __get__

>>> increment.__get__(None, type(None)) 
<function increment at 0x00000000027797C8> 
>>> increment.__get__(o, type(o)) 
<bound method A.increment of <__main__.A object at 0x00000000027669B0>> 

y funciona como un encanto:

>>> o = A() 
>>> increment.__get__(None, type(None))(o) 
>>> o.count 
1 
>>> increment.__get__(o, type(o))() 
>>> o.count 
2 

Usted puede agregar fácilmente estos métodos recién delimitadas a objetos:

def increment(obj): 
    obj.count += 1 

def addition(obj, number): 
    obj.count += number 

class A(object): 
    def __init__(self): 
     self.count = 0 

o = A() 
o.inc = increment.__get__(o) 
o.add = addition.__get__(o) 
print(o.count) # 0 
o.inc() 
print(o.count) # 1 
o.add(5) 
print(o.count) # 6 

O puede crear su propia descriptor que se va a convertir función a método vinculado:

class BoundMethod(object): 
    def __init__(self, function): 
     self.function = function 

    def __get__(self, obj, objtype=None): 
     print('Getting', obj, objtype) 
     return self.function.__get__(obj, objtype) 

class B(object): 
    def __init__(self): 
     self.count = 0 

    inc = BoundMethod(increment) 
    add = BoundMethod(addition) 


o = B() 
print(o.count) # 0 
o.inc() 
# Getting <__main__.B object at 0x0000000002677978> <class '__main__.B'> 
print(o.count) # 1 
o.add(5) 
# Getting <__main__.B object at 0x0000000002677978> <class '__main__.B'> 
print(o.count) # 6 

Y también se puede ver que esto es bien consistente con function/bound method principles:

Clase los diccionarios almacenan métodos como funciones. En una definición de clase, los métodos se escriben usando def y lambda, las herramientas usuales para crear funciones. La única diferencia con las funciones normales es que el primer argumento está reservado para la instancia del objeto. Según la convención de Python, la referencia de instancia se llama self pero se le puede llamar este o cualquier otro nombre de variable.

Para admitir llamadas de método, las funciones incluyen el método __get__() para métodos de enlace durante el acceso de atributo. Esto significa que todas las funciones son descripciones que no son de datos que devuelven métodos vinculados o no dependientes, dependiendo de si se invocan desde un objeto o una clase.

Y funciones se convierte en método vinculado durante la inicialización de instancia:

>>> B.add 
# Getting None <class '__main__.B'> 
<function addition at 0x00000000025859C8> 
>>> o.add 
# Getting <__main__.B object at 0x00000000030B1128> <class '__main__.B'> 
<bound method B.addition of <__main__.B object at 0x00000000030B1128>> 
+0

Normalmente, un tipo incorporado en CPython utiliza un 'method_descriptor', que se enlaza como' builtin_function_or_method', p. Ej. 'str.upper .__ get __ ('a') .__ self__ == 'a''.El 'builtin_function_or_method' en sí mismo es * no * un descriptor. Esto último es lo que crea Cython, por lo que el OP está buscando una forma de envolverlo en un descriptor como ['partialmethod'] (https://docs.python.org/3/library/functools.html#functools.partialmethod) (3.4+). – eryksun

+0

Creo que tu respuesta no es lo que quiere OP. – laike9m

Cuestiones relacionadas