Vra

Ek het gelees dat dit moontlik is om 'n metode by 'n bestaande voorwerp (dit wil sê nie in die klasdefinisie nie) in Python te voeg.

Ek verstaan ​​dat dit nie altyd goed is om dit te doen nie.Maar hoe kan 'n mens dit doen?

Was dit nuttig?

Oplossing

In Python is daar 'n verskil tussen funksies en gebonde metodes.

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

Gebonde metodes is "gebonde" (hoe beskrywend) aan 'n instansie, en daardie instansie sal as die eerste argument deurgegee word wanneer die metode geroep word.

Oproepbare wat kenmerke van 'n klas is (in teenstelling met 'n instansie) is egter steeds ongebonde, so jy kan die klasdefinisie verander wanneer jy wil:

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

Vooraf gedefinieerde gevalle word ook opgedateer (solank hulle nie die kenmerk self oorskryf het nie):

>>> a.fooFighters()
fooFighters

Die probleem kom wanneer jy 'n metode aan 'n enkele geval wil heg:

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

Die funksie word nie outomaties gebind wanneer dit direk aan 'n instansie geheg is nie:

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

Om dit te bind, kan ons die gebruik MethodType funksie in die tipes module:

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

Hierdie keer is ander gevalle van die klas nie geraak nie:

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

Meer inligting kan gevind word deur te lees oor beskrywers en metaklas Programmering.

Ander wenke

Module nuut is afgekeur sedert python 2.6 en verwyder in 3.0, gebruik tipes

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

In die voorbeeld hieronder het ek doelbewus terugkeerwaarde van verwyder patch_me() funksie.Ek dink dat die gee van terugkeerwaarde 'n mens kan laat glo dat pleister 'n nuwe voorwerp terugstuur, wat nie waar is nie - dit wysig die inkomende een.Waarskynlik kan dit 'n meer gedissiplineerde gebruik van monkeypatching fasiliteer.

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

Voorwoord - 'n nota oor verenigbaarheid:ander antwoorde mag dalk net in Python 2 werk - hierdie antwoord behoort heeltemal goed te werk in Python 2 en 3.As u slegs Python 3 skryf, kan u uitdruklik erf object, maar andersins moet die kode dieselfde bly.

Voeg 'n metode by 'n bestaande objekinstansie

Ek het gelees dat dit moontlik is om 'n metode by 'n bestaande voorwerp te voeg (bv.nie in die klasdefinisie nie) in Python.

Ek verstaan ​​dat dit nie altyd 'n goeie besluit is om dit te doen nie. Maar, hoe kan 'n mens dit doen?

Ja, dit is moontlik - Maar nie aanbeveel nie

Ek beveel dit nie aan nie.Dit is 'n slegte idee.Moenie dit doen nie.

Hier is 'n paar redes:

  • Jy sal 'n gebonde voorwerp by elke instansie waaraan jy dit doen, byvoeg.As jy dit baie doen, sal jy waarskynlik baie geheue mors.Gebonde metodes word tipies net geskep vir die kort duur van hul oproep, en hulle hou dan op om te bestaan ​​wanneer vullis outomaties versamel word.As jy dit met die hand doen, sal jy 'n naambinding hê wat na die gebonde metode verwys - wat die vullisversameling daarvan tydens gebruik sal verhoed.
  • Voorwerpgevalle van 'n gegewe tipe het gewoonlik sy metodes op alle voorwerpe van daardie tipe.As jy metodes elders byvoeg, sal sommige gevalle daardie metodes hê en ander nie.Programmeerders sal dit nie verwag nie, en jy loop die risiko om die reël van die minste verrassing.
  • Aangesien daar ander baie goeie redes is om dit nie te doen nie, sal jy jouself ook 'n swak reputasie gee as jy dit doen.

Ek stel dus voor dat jy dit nie doen nie, tensy jy 'n baie goeie rede het. Dit is baie beter om die korrekte metode in die klasdefinisie te definieer of minder verkieslik om die klas direk te aap, soos volg:

Foo.sample_method = sample_method

Aangesien dit egter leersaam is, gaan ek jou 'n paar maniere wys om dit te doen.

Hoe dit gedoen kan word

Hier is 'n paar opstelkode.Ons het 'n klasdefinisie nodig.Dit kan ingevoer word, maar dit maak regtig nie saak nie.

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

Skep 'n instansie:

foo = Foo()

Skep 'n metode om daarby te voeg:

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

Metode niks (0) - gebruik die beskrywingsmetode, __get__

Gestippelde soektogte op funksies noem die __get__ metode van die funksie met die instansie, bind die objek aan die metode en skep dus 'n "gebonde metode."

foo.sample_method = sample_method.__get__(foo)

en nou:

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

Metode een - tipes.MetodeTipe

Voer eers tipes in, waaruit ons die metodekonstruktor sal kry:

import types

Nou voeg ons die metode by die instansie.Om dit te doen, benodig ons die MethodType-konstruktor van die types module (wat ons hierbo ingevoer het).

Die argument handtekening vir tipes.MetodeTipe is (function, instance, class):

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

en gebruik:

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

Metode twee:leksikale binding

Eerstens skep ons 'n omhulfunksie wat die metode aan die instansie bind:

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

gebruik:

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

Metode drie:functools.partial

'n Gedeeltelike funksie pas die eerste argument(e) toe op 'n funksie (en opsioneel sleutelwoordargumente), en kan later met die oorblywende argumente (en oorheersende sleutelwoordargumente) opgeroep word.Dus:

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

Dit maak sin as jy in ag neem dat gebonde metodes gedeeltelike funksies van die instansie is.

Ongebonde funksie as 'n objek-kenmerk - hoekom dit nie werk nie:

As ons probeer om die sample_method by te voeg op dieselfde manier as wat ons dit by die klas kan voeg, is dit ongebonde van die instansie, en neem nie die implisiete self as die eerste argument nie.

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

Ons kan die ongebonde funksie laat werk deur die instansie eksplisiet deur te gee (of enigiets, aangesien hierdie metode nie eintlik die self argumentveranderlike), maar dit sal nie in ooreenstemming wees met die verwagte handtekening van ander gevalle nie (as ons hierdie geval aap-patch):

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

Afsluiting

Jy ken nou verskeie maniere waarop jy kon doen dit, maar in alle erns – moenie dit doen nie.

Ek dink dat die bogenoemde antwoorde die kernpunt gemis het.

Kom ons hou 'n klas met 'n metode:

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

Nou, kom ons speel daarmee in ipython:

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

Ok, so m() word op een of ander manier 'n ongebonde metode van A.Maar is dit regtig so?

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

Dit blyk dat m() is net 'n funksie, waarna verwys word A klaswoordeboek - daar is geen towerkrag nie.Hoekom dan Vm gee ons 'n ongebonde metode?Dit is omdat die punt nie na 'n eenvoudige woordeboekopsoek vertaal word nie.Dit is de facto 'n oproep van A.__klas__.__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

Nou is ek nie uit my kop seker hoekom die laaste reël twee keer gedruk word nie, maar tog is dit duidelik wat daar aangaan.

Nou, wat die verstek __getattribute__ doen, is dat dit kontroleer of die kenmerk 'n sogenaamde beskrywer of nie, d.w.s.as dit 'n spesiale __get__ metode implementeer.As dit daardie metode implementeer, dan is wat teruggestuur word die resultaat van die oproep van daardie __get__ metode.Gaan terug na die eerste weergawe van ons A klas, dit is wat ons het:

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

En omdat Python-funksies die beskrywingsprotokol implementeer, as hulle namens 'n objek geroep word, bind hulle hulself aan daardie objek in hul __get__ metode.

Ok, so hoe om 'n metode by 'n bestaande voorwerp te voeg?As jy aanvaar dat jy nie omgee om klas te lap nie, is dit so eenvoudig soos:

B.m = m

Toe B.m "word" 'n ongebonde metode, danksy die beskrywer-magie.

En as jy 'n metode net by 'n enkele voorwerp wil voeg, dan moet jy self die masjinerie naboots deur tipes te gebruik.MetodeTipe:

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

Terloops:

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

In Python werk monkey patching gewoonlik deur 'n klas- of funksieshandtekening met jou eie te oorskryf.Hieronder is 'n voorbeeld uit die Zope Wiki:

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

Daardie kode sal 'n metode genaamd praat oorskryf/skep op die klas.In Jeff Atwood's onlangse plasing oor aappleistering.Hy wys 'n voorbeeld in C# 3.0 wat die huidige taal is wat ek vir werk gebruik.

Daar is ten minste twee maniere om 'n metode aan 'n instansie te koppel sonder 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>>

Nuttige skakels:
Datamodel – beroep op beskrywers
Beskrywing Hoe-om-gids - beroep beskrywers

Jy kan lambda gebruik om 'n metode aan 'n instansie te bind:

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

Uitset:

This is instance string

Wat jy soek is setattr Ek glo.Gebruik dit om 'n kenmerk op 'n voorwerp te stel.

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

Aangesien hierdie vraag vir nie-Python-weergawes gevra is, is hier JavaScript:

a.methodname = function () { console.log("Yay, a new method!") }

Konsolidering van Jason Pratt en die gemeenskap se wiki-antwoorde, met 'n blik op die resultate van verskillende metodes van binding:

Let veral op hoe die byvoeging van die binding as 'n klasmetode funksioneer werk, maar die verwysingsomvang is verkeerd.

#!/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>>

Persoonlik verkies ek die eksterne ADDMETHOD-funksieroete, aangesien dit my toelaat om ook dinamies nuwe metodename binne 'n iterator toe te ken.

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

Julle moet regtig kyk verbode vrugte, dit is 'n luislang-biblioteek wat ondersteuning bied om enige luislangklas, selfs stringe, te pleister.

Dit is eintlik 'n toevoeging tot die antwoord van "Jason Pratt"

Alhoewel Jason se antwoord werk, werk dit net as 'n mens 'n funksie by 'n klas wil voeg.Dit het nie vir my gewerk toe ek probeer het om 'n reeds bestaande metode van die .py-bronkodelêer te herlaai nie.

Dit het my eeue geneem om 'n oplossing te vind, maar die truuk lyk eenvoudig ...1. Stel die kode in die bronkode -lêer in. Die herlaaide metode sou in 'n ander naamruimte wees. Nou kan u voortgaan soos voorgestel deur "Jason Pratt" met behulp van die soorte. MetodType (...)

Voorbeeld:

# 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

Wat Jason Pratt geplaas het is korrek.

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

Soos jy kan sien, beskou Python b() nie anders as a().In Python is alle metodes net veranderlikes wat toevallig funksies is.

As dit van enige hulp kan wees, het ek onlangs 'n Python-biblioteek genaamd Gorilla vrygestel om die proses van aappleistering geriefliker te maak.

Gebruik 'n funksie needle() om 'n module met die naam te pleister guineapig gaan soos volg:

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

Maar dit sorg ook vir meer interessante gebruiksgevalle soos getoon in die Gereelde vrae van die dokumentasie.

Die kode is beskikbaar op GitHub.

Hierdie vraag is jare gelede geopen, maar hey, daar is 'n maklike manier om die binding van 'n funksie aan 'n klasinstansie te simuleer deur versierders te gebruik:

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

Daar, wanneer jy die funksie en die instansie aan die binderversierder deurgee, sal dit 'n nuwe funksie skep, met dieselfde kode-objek as die eerste een.Dan word die gegewe geval van die klas gestoor in 'n kenmerk van die nuutgeskepte funksie.Die versierder gee 'n (derde) funksie terug wat outomaties die gekopieerde funksie oproep, en gee die instansie as die eerste parameter.

Ten slotte kry jy 'n funksie wat simuleer dat dit aan die klasinstansie bind.Laat die oorspronklike funksie onveranderd.

Ek vind dit vreemd dat niemand genoem het dat al die metodes hierbo gelys 'n siklusverwysing skep tussen die bygevoegde metode en die instansie, wat veroorsaak dat die voorwerp aanhoudend is tot vullisversameling.Daar was 'n ou truuk om 'n beskrywing by te voeg deur die klas van die voorwerp uit te brei:

def addmethod(obj, name, func):
    klass = obj.__class__
    subclass = type(klass.__name__, (klass,), {})
    setattr(subclass, name, func)
    obj.__class__ = subclass
from types import MethodType

def method(self):
   print 'hi!'


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

Hiermee kan jy die selfwyser gebruik

Gelisensieer onder: CC-BY-SA met toeskrywing
Nie verbonde aan StackOverflow
scroll top