Dodawanie metody do istniejącej instancji obiektu

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

Rozumiem, że nie zawsze dobrze jest to robić. Ale jak można to zrobić?

Author: Paul Ratazzi, 2008-08-04

17 answers

W Pythonie Istnieje różnica między funkcjami a metodami wiązanymi.

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

Bound methods have been "bound" (jak opisowy) to a instance, and that instance will be passed as the first argument where the method is called.

Wywołania, które są atrybutami klasy (w przeciwieństwie do instancji), są nadal niezwiązane, więc możesz modyfikować 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 również są aktualizowane (o ile nie nadpisałem samego atrybutu):

>>> a.fooFighters()
fooFighters

Problem pojawia się, gdy chcesz dołączyć metodę do pojedynczej instancji:

>>> 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 ją 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 inne instancje klasy nie zostały naruszone:

>>> 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 deskryptory i metaklass programowanie .

 773
Author: Jason Pratt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-24 23:47:39

Moduł new jest przestarzały od wersji python 2.6 i usunięty w wersji 3.0, użyj typów

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

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

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'>
 82
Author: Evgeny,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-07 13:22:40

Przedmowa-uwaga na temat kompatybilności: inne odpowiedzi mogą działać tylko w Pythonie 2 - Ta odpowiedź powinna działać doskonale w Pythonie 2 i 3. Jeśli piszesz tylko Python 3, możesz pominąć jawne dziedziczenie z object, ale w przeciwnym razie kod powinien pozostać taki 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.

I zrozum, że nie zawsze jest to dobra decyzja. Ale jak można to zrobić?

Tak, jest to możliwe - ale nie zalecane

Nie polecam tego. To zły pomysł. Nie rób tego. Oto kilka powodów:]}
  • dodasz związany obiekt do każdej instancji, do której to zrobisz. Jeśli robisz to często, prawdopodobnie zmarnujesz dużo pamięci. Metody wiązane są zazwyczaj tworzone tylko na krótki czas ich wywołania, a przestają wówczas istnieć po automatycznym zbieraniu śmieci. Jeśli zrobisz to ręcznie, będziesz mieć powiązanie nazwy odwołujące się do metody bound - co zapobiegnie zbieraniu śmieci przy użyciu.
  • instancje obiektów danego typu mają swoje metody na wszystkich obiektach tego typu. Jeśli dodasz metody gdzie indziej, niektóre instancje będą miały te metody, a inne nie. Programiści nie będą się tego spodziewać, a ty ryzykujesz złamanie zasady najmniejszego niespodzianka .
  • ponieważ są inne naprawdę dobre powody, aby tego nie robić, dodatkowo zyskasz złą reputację, jeśli to zrobisz.
Dlatego sugeruję, żebyś tego nie robił, chyba że masz naprawdę dobry powód. o wiele lepiej jest zdefiniować poprawną metodę w definicji klasy lub mniej najlepiej bezpośrednio dopasować klasę, tak:
Foo.sample_method = sample_method

Ponieważ jest to pouczające, pokażę Ci kilka sposobów robię to.

Jak można to zrobić

Oto kod konfiguracji. Potrzebujemy definicji klasy. To może być importowane, 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 dodać do niej:

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

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

Przerywane wyszukiwanie funkcji wywołuje metodę __get__ funkcji z instancją, wiążąc obiekt z metodą i tworząc w ten sposób " bound metoda."

foo.sample_method = sample_method.__get__(foo)

A teraz:

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

Metoda pierwsza-typy.MethodType

Najpierw import typów, z których otrzymamy konstruktor metody:

import types

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

Sygnatura argumentu dla typów.MethodType is (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

Po pierwsze, my Utwórz funkcję wrappera, 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

Użycie:

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

Metoda trzecia: functools.częściowe

Funkcja częściowa stosuje pierwszy argument(y) do funkcji (i opcjonalnie argumenty słów kluczowych), a później może być wywołana z pozostałymi argumentami (i nadrzędnymi argumentami słów kluczowych). Tak więc:

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

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

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

Jeśli spróbujemy dodać sample_method w taki sam sposób, w jaki możemy dodać ją do klasy, to jest ona niezwiązana z instancją i nie przyjmuje implicit self 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ć, że funkcja niezwiązana będzie działać jawnie przekazując instancję (lub cokolwiek innego, ponieważ ta metoda nie używa zmiennej argumentu self), ale nie będzie ona zgodna z oczekiwaną sygnaturą innych instancji( jeśli monkey-patching this instance):

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

Podsumowanie

Wiesz już na kilka sposobów, jak możesz to zrobić, ale z całą powagą-nie rób tego.
 54
Author: Aaron Hall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-12 15:06:04

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

Let ' s have a class with a method:

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

A teraz Pobawmy się nim w ipythonie:

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

Ok, więc m() w jakiś sposób staje się niezwiązaną metodą A . Ale czy naprawdę tak jest?

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

Okazuje się, że m () jest tylko funkcją, do której odniesienie jest dodawane do a class dictionary - nie ma magii. Więc dlaczego A. M daje nam niezwiązaną metodę? On ponieważ kropka nie jest tłumaczona na proste wyszukiwanie słownika. To de facto wezwanie A. _ _ class__.__ 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 linijka jest drukowana dwa razy, ale nadal jest jasne, co się tam dzieje.

Domyślnie__ getattribute _ _ sprawdza, czy atrybut jest tak zwanym deskryptorem , czy nie, tzn. czy implementuje specjalną metodę _ _ get__. Jeśli implementuje tę metodę, to co jest zwracane jest wynikiem wywołania metody _ _ get__. Wracając do pierwszej wersji naszej klasy a , Oto co mamy:

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 w swojej metodzie _ _ get__.

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

B.m = m

Wtedy B. M "staje się" an metoda niezwiązana, dzięki magii deskryptora.

I jeśli chcesz dodać metodę tylko do jednego obiektu, musisz sam emulować maszynę, używając typów.MethodType:

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

Przy okazji:

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'>
 31
Author: Tomasz Zielinski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-06 16:47:01

W Pythonie łatanie małp zazwyczaj polega na nadpisaniu podpisu klasy lub funkcji własną. Poniżej znajduje się przykład z Zope Wiki :

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 Monkey patching . Pokazuje przykład w C # 3.0, który jest aktualnym językiem, którego używam w pracy.

 15
Author: John Downey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-04 02:31:13

Istnieją co najmniej dwa sposoby dołączania 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 Przewodnik HowTo-wywoływanie deskryptorów

 9
Author: ndpu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-26 15:47:35

Możesz użyć lambda, aby powiązać metodę 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()

To jest łańcuch instancji

Proces zakończony kodem wyjścia 0

 7
Author: Evgeny Prokurat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-21 12:55:54

To, czego szukasz, to setattr wierzę. Użyj tego, aby ustawić atrybut na obiekcie.

>>> 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>
 6
Author: HS.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-07 11:30:16

Ponieważ to pytanie zadane dla wersji innych niż Python, Oto JavaScript:

a.methodname = function () { console.log("Yay, a new method!") }
 6
Author: Thom Blake,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-09 15:07:08

Konsolidacja odpowiedzi Jasona Pratta i Społeczności wiki, z spojrzeniem na wyniki różnych metod wiązania:

Zwróć szczególną uwagę na to, jak dodawanie funkcji wiążącej jako metody klasy działa, ale zakres odniesienia 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>>
 5
Author: Nisan.H,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-28 00:18:28

Powinniście zajrzeć do forbidden fruit , jest to biblioteka Pythona, która zapewnia wsparcie dla łatania każdej klasy Pythona, nawet ciągów.

 5
Author: Gabriel Falcão,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-25 21:56:46

Jest to dodatek do odpowiedzi "Jason Pratt"

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

Zajęło mi wieki znalezienie obejścia, ale sztuczka wydaje się prosta... 1.st Importuj kod z pliku kodu źródłowego 2.ND force a reload 3.typy zastosowań rd.FunctionType(...) aby przekonwertować zaimportowaną i powiązaną metodę na funkcja możesz również przekazać bieżące zmienne globalne, ponieważ metoda przeładowana byłaby w innej przestrzeni nazw 4.th teraz możesz kontynuować zgodnie z sugestią " Jason Pratt" korzystanie z 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
 5
Author: Max,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-18 15:32:47

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 widzisz, Python nie uważa b() za inną niż a (). W Pythonie wszystkie metody są tylko zmiennymi, które są funkcjami.

 3
Author: Acuminate,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-22 14:40:21

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

Używanie funkcji needle() do łatania modułu o nazwie guineapig wygląda 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 .

 3
Author: ChristopherC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-15 02:12:30

To pytanie zostało otwarte lata temu, ale jest łatwy sposób na symulację wią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, po przekazaniu funkcji I instancji do dekoratora binder, utworzy ona nową funkcję, z tym samym obiektem kodu, co pierwszy. Następnie dana instancja klasy jest przechowywana w atrybucie nowo utworzonej funkcji. Dekorator zwraca (trzecią) funkcję wywołującą automatycznie skopiowaną funkcję, podając instancję jako pierwszy parametr.

Podsumowując otrzymujemy funkcję symulującą, że jest ona powiązana z instancją klasy. Pozostawienie pierwotnej funkcji bez zmian.

 2
Author: lain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-21 21:39:00

Wydaje mi się dziwne, że nikt nie wspomniał o tym, że wszystkie metody wymienione powyżej tworzą odniesienie do cyklu pomiędzy dodaną metodą a instancją, powodując, że obiekt jest trwały do momentu usunięcia śmieci. Istnieje stara sztuczka dodawania deskryptora poprzez rozszerzenie klasy obiektu:

def addmethod(obj, name, func):
    klass = obj.__class__
    subclass = type(klass.__name__, (klass,), {})
    setattr(subclass, name, func)
    obj.__class__ = subclass
 2
Author: Yu Feng,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-30 04:57:29
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 self

 1
Author: Arturo Morales Rangel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-27 04:21:24