2010-09-27 12 views
9

Betrachten Sie den folgenden (gebrochen) Code:Konvertieren Teilfunktion Methode 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) 

Was ich tun möchte, ist die Funktion f nehmen und es teilweise gelten, in Abhängigkeit resultierende g(self,b). Ich möchte diese Funktion als Methode verwenden, jedoch ist dies derzeit nicht arbeiten und stattdessen erhalte ich die Fehler

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

tun x.g(x,2) jedoch funktioniert, so dass es das Problem scheint, dass g eine „normale“ Funktion betrachtet wird anstelle einer Methode der Klasse. Gibt es eine Möglichkeit, x.g anstelle einer Funktion wie eine Methode zu verhalten (d. H. Den Parameter self implizit zu übergeben)?

Antwort

9

Dies wird funktionieren. Aber ich bin mir nicht sicher, ob dies das ist, was Sie suchen.

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

Dumm mir, ziemlich offensichtlich im Nachhinein! – pafcu

+2

-1 Da dies eine Problemumgehung ist, aber es ist keine echte Methode - es ist nur eine aufrufbar auf der Instanz gespeichert. Entscheidend ist, dass 'Foo.g' nicht existiert. – wim

+0

@wim OP gibt nicht an, ob g für alle Objekte dieser Klasse gleich sein soll –

19

Hier sind zwei Probleme zur Hand. Erstens muss eine Funktion, die in eine Methode umgewandelt werden soll, in der Klasse gespeichert werden, nicht in der Instanz. Eine Demonstration:

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 

So a ist eine Funktion in der Klassendefinition definiert, ist b eine Funktion der Klasse zugewiesen danach, und c ist eine Funktion zumInstanz zugewiesen. Werfen Sie einen Blick auf das, was passiert, wenn wir sie nennen:

>>> 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',) 

Wie Sie es wenig Unterschied zwischen einer Funktion sehen kann zusammen mit der Klasse definiert, und ein später zugeordnet. Ich glaube, es gibt eigentlich keinen Unterschied, solange es keine Metaklasse gibt, aber das ist für eine andere Zeit.

Das zweite Problem kommt von der Tatsache, wie eine Funktion tatsächlich in eine Methode umgewandelt wird; Der Funktionstyp implementiert das Deskriptorprotokoll. (See the docs for details.) Kurz gesagt, der Funktionstyp verfügt über eine spezielle -Methode, die aufgerufen wird, wenn Sie eine Attributsuche für die Klasse selbst durchführen. Anstatt das Funktionsobjekt abzurufen, wird die Methode dieses Funktionsobjekts aufgerufen, die ein gebundenes Methodenobjekt zurückgibt (was das Argument self liefert).

Warum ist das ein Problem? Weil das Objekt functools.partial kein Deskriptor ist!

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

Es gibt eine Reihe von Optionen, die Sie an dieser Stelle haben.

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) 

... oder würden Sie die Selbst und den Wert eines in einem Verschluss einzuzubetten: Sie können explizit das Selbst Argument für den Teil liefern

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

x = Foo() 
x.g(2) 

Diese Lösungen sind natürlich unter der Annahme, dass es ist ein noch unspezifizierter Grund dafür, der Klasse im Konstruktor eine Methode wie diese zuzuordnen, da Sie sehr einfach eine Methode direkt auf der Klasse definieren können, um das zu tun, was Sie hier tun.

Edit: Hier ist eine Idee für eine Lösung, die die Funktionen der Annahme kann für die Klasse erstellt werden, anstelle der Instanz:

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

Sie gibt:

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

Ich erstelle tatsächlich eine Menge von Teilfunktionen in einer Schleife (mit unterschiedlichen Werten von a), so dass die zweite Methode nicht funktionieren würde (Ich denke?) Da Sie vermuten, dass dies natürlich eine Vereinfachung dessen ist, was ich mache, erstelle ich keine Teilfunktionen, um eine Konstante zu einem Wert hinzuzufügen. – pafcu

+0

@pafcu: Was versuchst du eigentlich zu tun? = P –

+0

Bindungen zur Laufzeit für eine Anwendung generieren. Grundsätzlich bekomme ich eine Datei mit "Funktionssignaturen" und ich generiere Methoden, die Befehle an die Anwendung senden. Ich habe eine Basisfunktion ("f"), die einen Befehl ("a") erhält und an eine externe Anwendung sendet. Die Basisfunktion benötigt Zugriff auf einen Dateideskriptor für die in der Klasseninstanz gespeicherte App (benötigt also "self"). Aufgrund partieller Anwendung wird die Funktion in eine Methode umgewandelt, die beim Aufruf den richtigen Befehl sendet. Ich hoffe, dass dies Ihre Neugier befriedigt :-) – pafcu

5

dies einfach ein konkretes Beispiel dessen, was ich glaube, ist der korrekteste (und daher pythonisch :) Weg zu lösen - da die beste Lösung (Definition für eine Klasse!) nie enthüllt wurde - @MikeBoers Erklärungen sind ansonsten solide.

ich habe dieses Muster ziemlich oft benutzt (vor kurzem für eine API), und es hat unbeschreibliche Produktionszeiten ohne die geringste Unregelmäßigkeit überlebt.

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) 

... ergibt:

10 
10 

... der Schlüssel Take-Home-Punkt hier ist MethodType(func, inst, cls), die ein ungebundenes Verfahren von einem anderen aufrufbar erstellt (Sie auch diese an der Kette verwenden können/bind Instanzmethoden an unabhängige Klassen ... wenn + instanziiert die ursprüngliche Instanz Methode erhalten BEIDEself Objekte!)

note genannt ex schlüssige Verwendung von Schlüsselwortargumenten! Während es einen besseren Weg geben könnte, zu handeln, sind Argumente im Allgemeinen eine PITA, weil die Platzierung von Selbst weniger vorhersehbar wird. auch, IME sowieso, mit *args, und **kwds in der untersten Funktion hat sich später als sehr nützlich erwiesen.

+0

Das funktioniert nicht auf Python 3; Das allerletzte Argument von MethodType wird zurückgewiesen, und MethodType scheint jetzt eine tatsächliche Instanz zu benötigen und wird "None" nicht annehmen. –

2

functools.partialmethod() ist seit Python 3.4 für diesen Zweck verfügbar.

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

Ein einfaches Code-Snippet ist nicht genug, geben Sie auch eine verbale Erklärung. – peterh

+0

Sollte dies nicht 'g = functools.partialmethod (f, 1)' sein? – quazgar