Dodawanie metody do istniejącej instancji obiektu

642

Przeczytałem, że w Pythonie można dodać metodę do istniejącego obiektu (tj. Nie w definicji klasy).

Rozumiem, że nie zawsze jest to dobre. Ale jak to zrobić?

akdom
źródło

Odpowiedzi:

921

W Pythonie istnieje różnica między funkcjami a powiązanymi metodami.

>>> def foo():
...     print "foo"
...
>>> class A:
...     def bar( self ):
...         print "bar"
...
>>> a = A()
>>> foo
<function foo at 0x00A98D70>
>>> a.bar
<bound method A.bar of <__main__.A instance at 0x00A9BC88>>
>>>

Metody powiązane zostały „powiązane” (jak opisowe) z instancją i ta instancja będzie przekazywana jako pierwszy argument przy każdym wywołaniu metody.

Odwołania, które są atrybutami klasy (w przeciwieństwie do instancji) są nadal niezwiązane, więc możesz zmodyfikować definicję klasy, kiedy tylko chcesz:

>>> def fooFighters( self ):
...     print "fooFighters"
...
>>> A.fooFighters = fooFighters
>>> a2 = A()
>>> a2.fooFighters
<bound method A.fooFighters of <__main__.A instance at 0x00A9BEB8>>
>>> a2.fooFighters()
fooFighters

Wcześniej zdefiniowane instancje są również aktualizowane (o ile same nie zastąpiły atrybutu):

>>> a.fooFighters()
fooFighters

Problem pojawia się, gdy chcesz dołączyć metodę do pojedynczego wystąpienia:

>>> def barFighters( self ):
...     print "barFighters"
...
>>> a.barFighters = barFighters
>>> a.barFighters()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: barFighters() takes exactly 1 argument (0 given)

Funkcja nie jest automatycznie powiązana, gdy jest dołączona bezpośrednio do instancji:

>>> a.barFighters
<function barFighters at 0x00A98EF0>

Aby to powiązać, możemy użyć funkcji MethodType w module typów :

>>> import types
>>> a.barFighters = types.MethodType( barFighters, a )
>>> a.barFighters
<bound method ?.barFighters of <__main__.A instance at 0x00A9BC88>>
>>> a.barFighters()
barFighters

Tym razem nie dotyczy to innych instancji klasy:

>>> a2.barFighters()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: A instance has no attribute 'barFighters'

Więcej informacji można znaleźć, czytając o deskryptorach i programowaniu metaklasy .

Jason Pratt
źródło
65
Zamiast ręcznie utworzyć a MethodType, ręcznie wywołaj protokół deskryptora i niech funkcja wygeneruje twoją instancję: barFighters.__get__(a)tworzy powiązaną metodę barFighterspowiązania z a.
Martijn Pieters
2
@MartijnPieters wszelkie zalety korzystania z descriptor protocolvs tworzą na MethodTypebok być może nieco bardziej czytelne.
EndermanAPM
17
@EndermanAPM: Kilka: bardziej prawdopodobne jest, że będzie nadal działał dokładnie tak samo, jak robi to dostęp do atrybutu w instancji. To będzie pracować dla classmethodi staticmethodoraz inne deskryptory też. Pozwala to uniknąć zaśmiecania przestrzeni nazw kolejnym importem.
Martijn Pieters
34
Pełny kod sugerowanego podejścia deskryptora toa.barFighters = barFighters.__get__(a)
eqzx
98

Nowy moduł jest przestarzały od Pythona 2.6 i usunięty w 3.0, użyj typów

patrz http://docs.python.org/library/new.html

W poniższym przykładzie celowo usunąłem wartość zwracaną z patch_me()funkcji. Myślę, że podanie wartości zwracanej może sprawić, że uwierzysz, że łatka zwraca nowy obiekt, co nie jest prawdą - modyfikuje przychodzący. Prawdopodobnie może to ułatwić bardziej zdyscyplinowane korzystanie z monkeypatchingu.

import types

class A(object):#but seems to work for old style objects too
    pass

def patch_me(target):
    def method(target,x):
        print "x=",x
        print "called from", target
    target.method = types.MethodType(method,target)
    #add more if needed

a = A()
print a
#out: <__main__.A object at 0x2b73ac88bfd0>  
patch_me(a)    #patch instance
a.method(5)
#out: x= 5
#out: called from <__main__.A object at 0x2b73ac88bfd0>
patch_me(A)
A.method(6)        #can patch class too
#out: x= 6
#out: called from <class '__main__.A'>
Evgeny
źródło
1
Jak by to działało, gdyby dodawana metoda musiała odnosić się do siebie? Moja pierwsza próba prowadzi do błędu składniowego, ale wydaje się, że dodanie self w definicji metody nie działa. typy importu klasa A (obiekt): #, ale wydaje się, że działa również dla obiektów starego stylu ax = 'ax' pass def patch_me (target): metoda def (cel, x): print (self.ax) print ("x =" , x) print („wywoływany z”, cel) target.method = types.MethodType (metoda, cel) # w razie potrzeby dodaj więcej a = A () drukuj (a.ax)
WesR
85

Przedmowa - uwaga na temat zgodności: inne odpowiedzi mogą działać tylko w Pythonie 2 - ta odpowiedź powinna działać idealnie w Pythonie 2 i 3. Jeśli piszesz tylko w Pythonie 3, możesz pominąć dziedziczenie object, ale w przeciwnym razie kod powinien pozostać ten sam .

Dodawanie metody do istniejącej instancji obiektu

Czytałem, że można dodać metodę do istniejącego obiektu (np. Nie w definicji klasy) w Pythonie.

Rozumiem, że nie zawsze jest to dobra decyzja. Ale jak to zrobić?

Tak, jest to możliwe - ale nie zalecane

Nie polecam tego. To jest zły pomysł. Nie rób tego

Oto kilka powodów:

  • Do każdego wystąpienia, do którego to zrobisz, dodasz powiązany obiekt. Jeśli często to robisz, prawdopodobnie zmarnujesz dużo pamięci. Metody powiązane są zazwyczaj tworzone tylko na krótki czas ich wywołania, a następnie przestają istnieć, gdy automatycznie są usuwane śmieci. Jeśli zrobisz to ręcznie, będziesz mieć powiązanie nazwy odwołujące się do metody powiązanej - co zapobiegnie jej odśmiecaniu podczas użycia.
  • Instancje obiektów danego typu mają na ogół swoje metody na wszystkich obiektach tego typu. Jeśli dodasz metody w innym miejscu, niektóre instancje będą miały te metody, a inne nie. Programiści nie będą się tego spodziewać, a Ty ryzykujesz naruszeniem zasady najmniejszego zaskoczenia .
  • Ponieważ istnieją inne naprawdę dobre powody, aby tego nie robić, dodatkowo zyskasz złą reputację, jeśli to zrobisz.

Dlatego sugeruję, abyś tego nie robił, chyba że masz naprawdę dobry powód. Zdecydowanie lepiej jest zdefiniować poprawną metodę w definicji klasy, lub mniej korzystnie, aby łatać klasę bezpośrednio, tak jak to:

Foo.sample_method = sample_method

Ponieważ jest to pouczające, pokażę ci kilka sposobów na zrobienie tego.

Jak można to zrobić

Oto kod instalacyjny. Potrzebujemy definicji klasy. Można go zaimportować, ale to naprawdę nie ma znaczenia.

class Foo(object):
    '''An empty class to demonstrate adding a method to an instance'''

Utwórz instancję:

foo = Foo()

Utwórz metodę, aby ją dodać:

def sample_method(self, bar, baz):
    print(bar + baz)

Metoda naught (0) - użyj metody deskryptora, __get__

Kropkowane wyszukiwania funkcji wywołują __get__metodę funkcji z instancją, wiążąc obiekt z metodą i tworząc w ten sposób „powiązaną metodę”.

foo.sample_method = sample_method.__get__(foo)

i teraz:

>>> foo.sample_method(1,2)
3

Metoda pierwsza - types.MethodType

Najpierw zaimportuj typy, z których otrzymamy konstruktor metod:

import types

Teraz dodajemy metodę do instancji. Aby to zrobić, potrzebujemy konstruktora MethodType z typesmodułu (który zaimportowaliśmy powyżej).

Podpis argumentu dla typów.MethodType to (function, instance, class):

foo.sample_method = types.MethodType(sample_method, foo, Foo)

i użycie:

>>> foo.sample_method(1,2)
3

Metoda druga: wiązanie leksykalne

Najpierw tworzymy funkcję otoki, która wiąże metodę z instancją:

def bind(instance, method):
    def binding_scope_fn(*args, **kwargs): 
        return method(instance, *args, **kwargs)
    return binding_scope_fn

stosowanie:

>>> foo.sample_method = bind(foo, sample_method)    
>>> foo.sample_method(1,2)
3

Metoda trzecia: funkools.partial

Funkcja częściowa stosuje pierwsze argumenty do funkcji (i opcjonalnie argumenty słów kluczowych), a później można je wywołać z pozostałymi argumentami (i nadpisując argumenty słów kluczowych). A zatem:

>>> from functools import partial
>>> foo.sample_method = partial(sample_method, foo)
>>> foo.sample_method(1,2)
3    

Ma to sens, gdy weźmie się pod uwagę, że powiązane metody są częściowymi funkcjami instancji.

Funkcja niezwiązana jako atrybut obiektu - dlaczego to nie działa:

Jeśli spróbujemy dodać metodę_próbki w taki sam sposób, w jaki moglibyśmy dodać ją do klasy, jest ona niezwiązana z instancją i nie przyjmuje domyślnego „ja” jako pierwszego argumentu.

>>> foo.sample_method = sample_method
>>> foo.sample_method(1,2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: sample_method() takes exactly 3 arguments (2 given)

Możemy sprawić, by funkcja niezwiązana działała, jawnie przekazując instancję (lub cokolwiek innego, ponieważ ta metoda tak naprawdę nie używa selfzmiennej argumentu), ale nie byłaby zgodna z oczekiwaną sygnaturą innych instancji (jeśli łatamy małpy to wystąpienie):

>>> foo.sample_method(foo, 1, 2)
3

Wniosek

Znasz teraz kilka sposobów, aby to zrobić, ale z całą powagą - nie rób tego.

Aaron Hall
źródło
1
Zastrzeżenie co Zastanawiałem się. Definicja metod to po prostu funkcje zagnieżdżone w definicji klasy.
Atcold
1
@Atcold Rozszerzyłem powody, dla których warto tego uniknąć we wstępie.
Aaron Hall
__get__Metoda wymaga również klasę jako następnego parametru: sample_method.__get__(foo, Foo).
Aidas Bendoraitis
2
@AidasBendoraitis Nie powiedziałbym, że „potrzebuje” tego, jest to parametr opcjonalny, który jest podawany podczas stosowania protokołu deskryptora - ale funkcje python nie używają argumentu: github.com/python/cpython/blob/master/Objects/funcobject .c # L581
Aaron Hall
Mój komentarz został oparty na tym odnośniku: python-reference.readthedocs.io/en/latest/docs/dunderdsc/... To, co teraz widzę z oficjalnych dokumentów, jest opcjonalne: docs.python.org/3/howto/descriptor.html# protokół deskryptora
Aidas Bendoraitis
35

Myślę, że powyższe odpowiedzi pominęły kluczowy punkt.

Zróbmy klasę z metodą:

class A(object):
    def m(self):
        pass

Teraz zagrajmy z nim w ipython:

In [2]: A.m
Out[2]: <unbound method A.m>

Ok, m () w jakiś sposób staje się niezwiązany metoda A . Ale czy to naprawdę tak?

In [5]: A.__dict__['m']
Out[5]: <function m at 0xa66b8b4>

Okazuje się, że m () to tylko funkcja, do której odniesienie jest dodane do słownika klasy A - nie ma magii. Więc dlaczego Am daje nam sposób niezwiązany? To dlatego, że kropka nie jest tłumaczona na proste wyszukiwanie w słowniku. Jest to de facto wywołanie klasy A .__ __.__ getattribute __ (A, 'm'):

In [11]: class MetaA(type):
   ....:     def __getattribute__(self, attr_name):
   ....:         print str(self), '-', attr_name

In [12]: class A(object):
   ....:     __metaclass__ = MetaA

In [23]: A.m
<class '__main__.A'> - m
<class '__main__.A'> - m

Teraz nie jestem pewien, dlaczego ostatnia linia jest drukowana dwukrotnie, ale nadal jest jasne, co się tam dzieje.

Teraz domyślna funkcja __getattribute__ polega na sprawdzeniu, czy atrybut jest tak zwanym deskryptorem, czy nie, tj. Czy implementuje specjalną metodę __get__. Jeśli implementuje tę metodę, zwracany jest wynik wywołania tej metody __get__. Wracając do pierwszej wersji naszej klasy A , mamy to:

In [28]: A.__dict__['m'].__get__(None, A)
Out[28]: <unbound method A.m>

A ponieważ funkcje Pythona implementują protokół deskryptora, jeśli są wywoływane w imieniu obiektu, wiążą się z tym obiektem za pomocą metody __get__.

Ok, więc jak dodać metodę do istniejącego obiektu? Zakładając, że nie masz nic przeciwko łataniu klasy, jest to tak proste jak:

B.m = m

Wtedy Bm „staje się” metodą niezwiązaną dzięki magii deskryptorów.

A jeśli chcesz dodać metodę tylko do jednego obiektu, musisz sam emulować maszynerię, używając types.MethodType:

b.m = types.MethodType(m, b)

Tak poza tym:

In [2]: A.m
Out[2]: <unbound method A.m>

In [59]: type(A.m)
Out[59]: <type 'instancemethod'>

In [60]: type(b.m)
Out[60]: <type 'instancemethod'>

In [61]: types.MethodType
Out[61]: <type 'instancemethod'>
Tomasz Zieliński
źródło
19

W Pythonie łatanie małp na ogół działa poprzez nadpisanie podpisu klasy lub funkcji własnym. Poniżej znajduje się przykład z Wiki Zope :

from SomeOtherProduct.SomeModule import SomeClass
def speak(self):
   return "ook ook eee eee eee!"
SomeClass.speak = speak

Ten kod nadpisze / utworzy metodę o nazwie speak na klasie. W ostatnim poście Jeffa Atwooda na temat łatania małp . Pokazuje przykład w C # 3.0, który jest bieżącym językiem, którego używam do pracy.

John Downey
źródło
6
Ale wpływa to na wszystkie wystąpienia klasy, nie tylko na jeden.
glglgl,
14

Możesz użyć lambda do powiązania metody z instancją:

def run(self):
    print self._instanceString

class A(object):
    def __init__(self):
        self._instanceString = "This is instance string"

a = A()
a.run = lambda: run(a)
a.run()

Wynik:

This is instance string
Evgeny Prokurat
źródło
9

Istnieją co najmniej dwa sposoby dołączenia metody do instancji bez types.MethodType:

>>> class A:
...  def m(self):
...   print 'im m, invoked with: ', self

>>> a = A()
>>> a.m()
im m, invoked with:  <__main__.A instance at 0x973ec6c>
>>> a.m
<bound method A.m of <__main__.A instance at 0x973ec6c>>
>>> 
>>> def foo(firstargument):
...  print 'im foo, invoked with: ', firstargument

>>> foo
<function foo at 0x978548c>

1:

>>> a.foo = foo.__get__(a, A) # or foo.__get__(a, type(a))
>>> a.foo()
im foo, invoked with:  <__main__.A instance at 0x973ec6c>
>>> a.foo
<bound method A.foo of <__main__.A instance at 0x973ec6c>>

2:

>>> instancemethod = type(A.m)
>>> instancemethod
<type 'instancemethod'>
>>> a.foo2 = instancemethod(foo, a, type(a))
>>> a.foo2()
im foo, invoked with:  <__main__.A instance at 0x973ec6c>
>>> a.foo2
<bound method instance.foo of <__main__.A instance at 0x973ec6c>>

Przydatne linki:
Model danych - wywoływanie deskryptorów
Deskryptor Poradnik - wywoływanie deskryptorów

ndpu
źródło
7

To, czego szukasz, to setattrwierzę. Użyj tego, aby ustawić atrybut obiektu.

>>> def printme(s): print repr(s)
>>> class A: pass
>>> setattr(A,'printme',printme)
>>> a = A()
>>> a.printme() # s becomes the implicit 'self' variable
< __ main __ . A instance at 0xABCDEFG>
HS.
źródło
8
To łata klasę A, a nie instancję a.
Ethan Furman
5
Czy istnieje powód, aby używać setattr(A,'printme',printme)zamiast po prostu A.printme = printme?
Tobias Kienzler,
1
Ma to sens, jeśli konstruuje się nazwę metody w czasie wykonywania.
rr-
6

Ponieważ pytanie dotyczy wersji innych niż Python, oto JavaScript:

a.methodname = function () { console.log("Yay, a new method!") }
Tamzin Blake
źródło
5

Konsolidacja odpowiedzi Jasona Pratta i społeczności wiki, z uwzględnieniem wyników różnych metod wiązania:

Szczególnie zwrócić uwagę w jaki sposób dodanie funkcji wiążącej jako metoda klasy dzieł , ale zakres przedstawieniu jest nieprawidłowy.

#!/usr/bin/python -u
import types
import inspect

## dynamically adding methods to a unique instance of a class


# get a list of a class's method type attributes
def listattr(c):
    for m in [(n, v) for n, v in inspect.getmembers(c, inspect.ismethod) if isinstance(v,types.MethodType)]:
        print m[0], m[1]

# externally bind a function as a method of an instance of a class
def ADDMETHOD(c, method, name):
    c.__dict__[name] = types.MethodType(method, c)

class C():
    r = 10 # class attribute variable to test bound scope

    def __init__(self):
        pass

    #internally bind a function as a method of self's class -- note that this one has issues!
    def addmethod(self, method, name):
        self.__dict__[name] = types.MethodType( method, self.__class__ )

    # predfined function to compare with
    def f0(self, x):
        print 'f0\tx = %d\tr = %d' % ( x, self.r)

a = C() # created before modified instnace
b = C() # modified instnace


def f1(self, x): # bind internally
    print 'f1\tx = %d\tr = %d' % ( x, self.r )
def f2( self, x): # add to class instance's .__dict__ as method type
    print 'f2\tx = %d\tr = %d' % ( x, self.r )
def f3( self, x): # assign to class as method type
    print 'f3\tx = %d\tr = %d' % ( x, self.r )
def f4( self, x): # add to class instance's .__dict__ using a general function
    print 'f4\tx = %d\tr = %d' % ( x, self.r )


b.addmethod(f1, 'f1')
b.__dict__['f2'] = types.MethodType( f2, b)
b.f3 = types.MethodType( f3, b)
ADDMETHOD(b, f4, 'f4')


b.f0(0) # OUT: f0   x = 0   r = 10
b.f1(1) # OUT: f1   x = 1   r = 10
b.f2(2) # OUT: f2   x = 2   r = 10
b.f3(3) # OUT: f3   x = 3   r = 10
b.f4(4) # OUT: f4   x = 4   r = 10


k = 2
print 'changing b.r from {0} to {1}'.format(b.r, k)
b.r = k
print 'new b.r = {0}'.format(b.r)

b.f0(0) # OUT: f0   x = 0   r = 2
b.f1(1) # OUT: f1   x = 1   r = 10  !!!!!!!!!
b.f2(2) # OUT: f2   x = 2   r = 2
b.f3(3) # OUT: f3   x = 3   r = 2
b.f4(4) # OUT: f4   x = 4   r = 2

c = C() # created after modifying instance

# let's have a look at each instance's method type attributes
print '\nattributes of a:'
listattr(a)
# OUT:
# attributes of a:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x000000000230FD88>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x000000000230FD88>>
# f0 <bound method C.f0 of <__main__.C instance at 0x000000000230FD88>>

print '\nattributes of b:'
listattr(b)
# OUT:
# attributes of b:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x000000000230FE08>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x000000000230FE08>>
# f0 <bound method C.f0 of <__main__.C instance at 0x000000000230FE08>>
# f1 <bound method ?.f1 of <class __main__.C at 0x000000000237AB28>>
# f2 <bound method ?.f2 of <__main__.C instance at 0x000000000230FE08>>
# f3 <bound method ?.f3 of <__main__.C instance at 0x000000000230FE08>>
# f4 <bound method ?.f4 of <__main__.C instance at 0x000000000230FE08>>

print '\nattributes of c:'
listattr(c)
# OUT:
# attributes of c:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x0000000002313108>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x0000000002313108>>
# f0 <bound method C.f0 of <__main__.C instance at 0x0000000002313108>>

Osobiście wolę zewnętrzną trasę funkcji ADDMETHOD, ponieważ pozwala mi dynamicznie przypisywać nowe nazwy metod również w iteratorze.

def y(self, x):
    pass
d = C()
for i in range(1,5):
    ADDMETHOD(d, y, 'f%d' % i)
print '\nattributes of d:'
listattr(d)
# OUT:
# attributes of d:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x0000000002303508>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x0000000002303508>>
# f0 <bound method C.f0 of <__main__.C instance at 0x0000000002303508>>
# f1 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
# f2 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
# f3 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
# f4 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
Nisan.H
źródło
addmethodprzepisany w następujący sposób def addmethod(self, method, name): self.__dict__[name] = types.MethodType( method, self )rozwiązuje problem
Antony Hatchkins
5

To właściwie dodatek do odpowiedzi „Jason Pratt”

Chociaż odpowiedź Jadesa działa, działa ona tylko wtedy, gdy chce się dodać funkcję do klasy. Nie działało to dla mnie, gdy próbowałem ponownie załadować już istniejącą metodę z pliku kodu źródłowego .py.

Znalezienie obejścia zajęło mi od wieków, ale sztuczka wydaje się prosta ... 1. zaimportuj kod z pliku kodu źródłowego 2. i wymuś przeładowanie 3. typy użycia. Funkcja Typ (...) do konwersji zaimportowaną i powiązaną metodę z funkcją można również przekazać bieżącym zmiennym globalnym, ponieważ ponownie załadowana metoda będzie w innej przestrzeni nazw 4. teraz można kontynuować zgodnie z sugestią „Jason Pratt”, używając typów.MethodType (... )

Przykład:

# this class resides inside ReloadCodeDemo.py
class A:
    def bar( self ):
        print "bar1"

    def reloadCode(self, methodName):
        ''' use this function to reload any function of class A'''
        import types
        import ReloadCodeDemo as ReloadMod # import the code as module
        reload (ReloadMod) # force a reload of the module
        myM = getattr(ReloadMod.A,methodName) #get reloaded Method
        myTempFunc = types.FunctionType(# convert the method to a simple function
                                myM.im_func.func_code, #the methods code
                                globals(), # globals to use
                                argdefs=myM.im_func.func_defaults # default values for variables if any
                                ) 
        myNewM = types.MethodType(myTempFunc,self,self.__class__) #convert the function to a method
        setattr(self,methodName,myNewM) # add the method to the function

if __name__ == '__main__':
    a = A()
    a.bar()
    # now change your code and save the file
    a.reloadCode('bar') # reloads the file
    a.bar() # now executes the reloaded code
Max
źródło
3

Jeśli to może pomóc, niedawno wydałem bibliotekę Python o nazwie Gorilla, aby ułatwić łatanie małp.

Korzystanie z funkcji needle()do łatania modułu o nazwie guineapigprzebiega następująco:

import gorilla
import guineapig
@gorilla.patch(guineapig)
def needle():
    print("awesome")

Ale zajmuje się również bardziej interesującymi przypadkami użycia, jak pokazano w FAQ z dokumentacji .

Kod jest dostępny na GitHub .

ChristopherC
źródło
3

To pytanie zostało otwarte lata temu, ale hej, istnieje prosty sposób na symulację powiązania funkcji z instancją klasy za pomocą dekoratorów:

def binder (function, instance):
  copy_of_function = type (function) (function.func_code, {})
  copy_of_function.__bind_to__ = instance
  def bound_function (*args, **kwargs):
    return copy_of_function (copy_of_function.__bind_to__, *args, **kwargs)
  return bound_function


class SupaClass (object):
  def __init__ (self):
    self.supaAttribute = 42


def new_method (self):
  print self.supaAttribute


supaInstance = SupaClass ()
supaInstance.supMethod = binder (new_method, supaInstance)

otherInstance = SupaClass ()
otherInstance.supaAttribute = 72
otherInstance.supMethod = binder (new_method, otherInstance)

otherInstance.supMethod ()
supaInstance.supMethod ()

Tam, gdy przekażesz funkcję i instancję do dekoratora spoiwa, utworzy ona nową funkcję, z tym samym obiektem kodu co pierwszy. Następnie dane wystąpienie klasy jest przechowywane w atrybucie nowo utworzonej funkcji. Dekorator zwraca (trzecią) funkcję wywołującą automatycznie skopiowaną funkcję, podając instancję jako pierwszy parametr.

Podsumowując, otrzymujesz funkcję symulującą jej wiązanie z instancją klasy. Pozostawienie oryginalnej funkcji niezmienionej.

rozpostarty
źródło
2

To, co napisał Jason Pratt, jest poprawne.

>>> class Test(object):
...   def a(self):
...     pass
... 
>>> def b(self):
...   pass
... 
>>> Test.b = b
>>> type(b)
<type 'function'>
>>> type(Test.a)
<type 'instancemethod'>
>>> type(Test.b)
<type 'instancemethod'>

Jak widać, Python nie uważa, że ​​b () różni się od a (). W Pythonie wszystkie metody są tylko zmiennymi, które okazały się funkcjami.

Spiczasty
źródło
7
Łatasz klasę Test, a nie jej instancję.
Ethan Furman,
Dodajesz metodę do klasy, a nie do instancji obiektu.
TomSawyer
2

Wydaje mi się dziwne, że nikt nie wspomniał, że wszystkie metody wymienione powyżej tworzą odwołanie do cyklu między dodaną metodą a instancją, powodując, że obiekt jest trwały aż do wyrzucania elementów bezużytecznych. Była stara sztuczka polegająca na dodaniu deskryptora przez rozszerzenie klasy obiektu:

def addmethod(obj, name, func):
    klass = obj.__class__
    subclass = type(klass.__name__, (klass,), {})
    setattr(subclass, name, func)
    obj.__class__ = subclass
Yu Feng
źródło
2
from types import MethodType

def method(self):
   print 'hi!'


setattr( targetObj, method.__name__, MethodType(method, targetObj, type(method)) )

Dzięki temu możesz użyć wskaźnika własnego

Arturo Morales Rangel
źródło