2010-09-27 16 views
9

Si consideri il seguente codice (rotto):Convertire funzione parziale al metodo in python

import functools                
class Foo(object):               
    def __init__(self):             
    def f(a,self,b):            
     print a+b           
    self.g = functools.partial(f,1)        
x=Foo()                  
x.g(2) 

Quello che voglio fare è prendere la funzione f e parzialmente applicarlo, con conseguente una funzione g(self,b). Vorrei utilizzare questa funzione come metodo, ma questo al momento non funziona e invece ottengo l'errore

Traceback (most recent call last): 
    File "test.py", line 8, in <module> 
    x.g(2) 
TypeError: f() takes exactly 3 arguments (2 given) 

Facendo x.g(x,2) però funziona, in modo da sembrare il problema è che G è considerata una funzione "normale" invece di un metodo della classe. C'è un modo per ottenere x.g di comportarsi come un metodo (cioè passare implicitamente il parametro self) invece di una funzione?

risposta

9

Questo funzionerà. Ma non sono sicuro se questo è quello che stai cercando

class Foo(object):               
    def __init__(self):             
    def f(a,self,b):            
     print a+b           
    self.g = functools.partial(f,1, self) # <= passing `self` also. 

x = Foo() 
x.g(2) 
+0

Stupido me, piuttosto ovvio col senno di poi! – pafcu

+2

-1 Poiché si tratta di una soluzione alternativa, ma non è un metodo reale: è solo un callable archiviato nell'istanza. Fondamentalmente, 'Foo.g' non esiste. – wim

+0

@wim OP non specifica se vogliono che g sia uguale per tutti gli oggetti di questa classe –

19

Ci sono due problemi a portata di mano qui. Innanzitutto, affinché una funzione sia trasformata in un metodo, deve essere memorizzata nella classe , non nell'istanza. Una dimostrazione:

class Foo(object): 
    def a(*args): 
     print 'a', args 

def b(*args): 
    print 'b', args 

Foo.b = b 

x = Foo() 

def c(*args): 
    print 'c', args 

x.c = c 

Così a è una funzione definita nella definizione della classe, b è una funzione assegnata alla classe dopo, e c è una funzione assegnata al istanza. Date un'occhiata a quello che succede quando li chiamiamo:

>>> x.a('a will have "self"') 
a (<__main__.Foo object at 0x100425ed0>, 'a will have "self"') 
>>> x.b('as will b') 
b (<__main__.Foo object at 0x100425ed0>, 'as will b') 
>>> x.c('c will only recieve this string') 
c ('c will only recieve this string',) 

Come potete vedere non v'è poca differenza tra una funzione definita insieme con la classe, e quello assegnato in un secondo momento. Credo che in realtà non ci sia alcuna differenza finché non ci sono metaclassi coinvolti, ma questo è per un'altra volta.

Il secondo problema deriva dal fatto che una funzione viene effettivamente trasformata in un metodo in primo luogo; il tipo di funzione implementa il protocollo descrittore. (See the docs for details.) In poche parole, il tipo di funzione ha un metodo speciale __get__ che viene chiamato quando si esegue una ricerca di attributi sulla classe stessa. Invece di ottenere l'oggetto funzione, viene chiamato il metodo __get__ di quell'oggetto funzione, che restituisce un oggetto metodo associato (che è ciò che fornisce l'argomento self).

Perché questo è un problema? Perché l'oggetto functools.partial non è un descrittore!

>>> import functools 
>>> def f(*args): 
...  print 'f', args 
... 
>>> g = functools.partial(f, 1, 2, 3) 
>>> g 
<functools.partial object at 0x10042f2b8> 
>>> g.__get__ 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
AttributeError: 'functools.partial' object has no attribute '__get__' 

Ci sono un certo numero di opzioni che hai a questo punto. È possibile fornire esplicitamente l'argomento sé al parziale:

import functools 
class Foo(object):               
    def __init__(self): 
     def f(self, a, b):            
      print a + b           
     self.g = functools.partial(f, self, 1) 

x = Foo() 
x.g(2) 

... o se volete incastrare il sé e il valore di una in una chiusura:

class Foo(object):               
    def __init__(self):             
     a = 1 
     def f(b):            
      print a + b           
     self.g = f 

x = Foo() 
x.g(2) 

Queste soluzioni sono ovviamente assumendo che non vi è un motivo ancora non specificato per assegnare un metodo alla classe nel costruttore come questo, poiché puoi facilmente definire semplicemente un metodo direttamente sulla classe per fare ciò che stai facendo qui.

Edit: Ecco un'idea per una soluzione svolgendo tutte le funzioni possono essere creati per la classe, anziché l'istanza:

class Foo(object): 
    pass 

def make_binding(name): 
    def f(self, *args): 
     print 'Do %s with %s given %r.' % (name, self, args) 
    return f 

for name in 'foo', 'bar', 'baz': 
    setattr(Foo, name, make_binding(name)) 

f = Foo() 
f.foo(1, 2, 3) 
f.bar('some input') 
f.baz() 

Ti dà:

Do foo with <__main__.Foo object at 0x10053e3d0> given (1, 2, 3). 
Do bar with <__main__.Foo object at 0x10053e3d0> given ('some input',). 
Do baz with <__main__.Foo object at 0x10053e3d0> given(). 
+0

In realtà sto creando un gruppo di funzioni parziali in un ciclo (con valori diversi di a), quindi il secondo metodo non funzionerebbe (Penso?) Come sospetti, questa è una semplificazione di quello che sto facendo, in realtà non sto creando funzioni parziali per aggiungere una costante a un valore. – pafcu

+0

@pafcu: cosa stai cercando di fare? = P –

+0

Genera collegamenti per un'applicazione in fase di esecuzione. Fondamentalmente ottengo un file contenente "firme di funzioni" e genero metodi che inviano comandi all'applicazione. Ho una funzione di base ("f") che ottiene un comando ("a") e la invia a un'applicazione esterna. La funzione di base deve accedere a un descrittore di file all'app memorizzata nell'istanza della classe (quindi è necessario "self"). A causa dell'applicazione parziale, la funzione diventa un metodo che invia il comando corretto quando viene chiamato. Spero che questo soddisfi la tua curiosità :-) – pafcu

5

questo è semplicemente un esempio concreto di ciò che ritengo sia il modo più corretto (e quindi pietistico :) di risolverlo - poiché la migliore soluzione (definizione di una classe!) non è mai stata rivelata - le spiegazioni di @MikeBoers sono altrimenti solide.

ho usato questo schema un po '(recentemente per un'API proxy), ed è sopravvissuto all'incirca ore di produzione senza la minima irregolarità.

from functools import update_wrapper 
from functools import partial 
from types import MethodType 


class Basic(object): 

    def add(self, **kwds): 
     print sum(kwds.values()) 

Basic.add_to_one = MethodType(
     update_wrapper(partial(Basic.add, a=1), Basic.add), 
     None, 
     Basic, 
     ) 


x = Basic() 
x.add(a=1, b=9) 
x.add_to_one(b=9) 

... rendimenti:

10 
10 

... il take-home-punto chiave qui è MethodType(func, inst, cls), che crea un metodo non legato da un altro richiamabile (si può anche usare questo per la catena/metodi di istanza legano alle classi non correlate ... quando un'istanza + chiamato il metodo di istanza originale riceverà ENTRAMBIself oggetti!)

nota ex uso clandestino degli argomenti delle parole chiave! mentre ci potrebbe essere un modo migliore per gestire, gli argomenti in generale sono una PITA perché la collocazione di sé diventa meno prevedibile. inoltre, IME comunque, utilizzando *args e **kwds nella funzione in basso è risultato utile in seguito.

+0

Questo non funziona su Python 3; l'ultimo argomento di MethodType viene rifiutato e MethodType sembra richiedere un'istanza attuale e non richiede "None". –

2

functools.partialmethod() disponibile a partire da Python 3.4 per questo scopo.

import functools                
class Foo(object):               
    def __init__(self):             
     def f(a,self,b):            
     print a+b           
    self.g = functools.partialmethod(f,1)        
x=Foo()                  
x.g(2) 
+0

Un snippet di codice semplice non è sufficiente, fornire anche una spiegazione verbale. – peterh

+0

Non dovrebbe essere 'g = functools.partialmethod (f, 1)'? – quazgar

Problemi correlati