2012-05-24 16 views
16

consideri una semplice funzione comeConvertire tipo funzione primitiva al tipo di metodo (in Python 3)

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

che è gestito attraverso Cython e compilato in un modulo di estensione. Supponiamo ora che mi piacerebbe fare di questa funzione un metodo su una classe. Ad esempio:

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

from compiled_extension import increment 
Counter.increment = increment 

Ora questo non funzionerà, poiché la convenzione di chiamata al livello C sarà interrotta. Per esempio:

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

Ma in Python 2, possiamo convertire la funzione di un metodo non legato facendo:

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

Come posso realizzare la stessa cosa in Python 3?

Un modo semplice è quello di utilizzare un wrapper sottile:

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) 

C'è un modo più efficace per farlo?

+2

Ho avuto questo problema cercando di costruire una classe Heapq utilizzando il brutto 'modulo di heapq'. La tua soluzione è buona. Può essere fatto in una riga, ma ha la stessa efficienza: 'def method_wraper (f): restituisce functools.wraps (f) (lambda * a, ** kw: f (* a, ** kw))' – JBernardo

+0

. .. Interessante, l'assegnazione sembra funzionare correttamente se la funzione è definita all'interno dello stesso modulo (metodo non associato che viene assegnato a una classe e associato a un'istanza). Quindi è solo un problema con le estensioni C, o con funzioni in diversi moduli? Ad ogni modo, potresti voler controllare http://stackoverflow.com/questions/7490879/python3-bind-method-to-class-instance-with-get-it-works-but-why, che potrebbe aiutarti un po ' anche. – JAB

+0

Inoltre, http://docs.python.org/py3k/howto/descriptor.html#functions-and-methods "l'effettiva implementazione C di PyMethod_Type in Objects/classobject.c è un singolo oggetto con due diverse rappresentazioni a seconda che il campo im_self è impostato o è NULL (l'equivalente C di None). " Il che fa sembrare che questo problema non si verifichi affatto, a meno che Python in qualche modo non aggiorni quel campo direttamente per i metodi di un oggetto durante l'istanziazione dell'oggetto. – JAB

risposta

0

Importa l'estensione in questo modo:

import compiled_extension 

Nella classe si scrive:

def increment: return compiled_extension.increment() 

Questo sembra più leggibile e potrebbe essere più efficiente.

4

La prima cosa è ottenere i nomi correttamente:

>>> 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> 

nomi propri sono Così funzioni e metodi legati. Ora si può guardare per come Bind an Unbound Method e probabilmente finirà per leggere su descriptors:

In generale, un descrittore è un attributo oggetto con "vincolante comportamento", uno il cui accesso attributo è stata sostituita con metodi nel protocollo descrittore. Questi metodi sono __get__, __set__ e __delete__. Se uno di questi metodi è definito per un oggetto, si dice che sia un descrittore.

Si può facilmente trasformare la funzione per il metodo da solo utilizzando invocazione diverso di __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>> 

e funziona come un fascino:

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

Si può facilmente aggiungere questi metodi di recente delimitate agli oggetti:

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 

Oppure creare il proprio descrittore che vi permette di convertire funzione-metodo vincolato:

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 

E si può anche vedere che questo è ben coerente con function/bound method principles:

Classe i dizionari memorizzano i metodi come funzioni. In una definizione di classe, i metodi vengono scritti utilizzando def e lambda, i soliti strumenti per creare funzioni. L'unica differenza rispetto alle normali funzioni è che il primo argomento è riservato all'istanza dell'oggetto. Con la convenzione Python, il riferimento all'istanza viene chiamato self ma può essere chiamato questo o qualsiasi altro nome di variabile.

Per supportare le chiamate di metodo, le funzioni includono il metodo __get__() per i metodi di associazione durante l'accesso agli attributi. Ciò significa che tutte le funzioni sono descrittori non di dati che restituiscono metodi associati o non associati a seconda che siano invocati da un oggetto o una classe.

E funzioni diventa metodo vincolato durante l'inizializzazione esempio:

>>> 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 built-in in CPython usa un 'method_descriptor', che viene associato come' builtin_function_or_method', ad es. 'str.upper .__ get __ ('a') .__ self__ == 'a''.Il 'builtin_function_or_method' stesso è * non * un descrittore. Quest'ultimo è ciò che crea Cython, in modo che il PO è alla ricerca di un modo per avvolgerlo in un descrittore come [ 'partialmethod'] (https://docs.python.org/3/library/functools.html#functools.partialmethod) (3.4+). – eryksun

+0

Penso che la tua risposta non sia ciò che l'OP vuole. – laike9m

Problemi correlati