既存のオブジェクトインスタンスへのメソッドの追加
-
08-06-2019 - |
質問
Python では既存のオブジェクト (つまり、クラス定義内ではない) にメソッドを追加できると読みました。
そうすることが必ずしも良いことではないことは理解しています。しかし、どうやってこれを行うことができるでしょうか?
解決
Python では、関数とバインドされたメソッドには違いがあります。
>>> 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>>
>>>
バインドされたメソッドはインスタンスに「バインド」されており (どの程度わかりやすいか)、メソッドが呼び出されるたびにそのインスタンスが最初の引数として渡されます。
ただし、(インスタンスではなく) クラスの属性である呼び出し可能オブジェクトはまだバインドされていないため、必要に応じていつでもクラス定義を変更できます。
>>> 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
以前に定義されたインスタンスも同様に更新されます (属性自体をオーバーライドしていない限り)。
>>> a.fooFighters()
fooFighters
問題は、メソッドを単一のインスタンスにアタッチする場合に発生します。
>>> 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)
関数がインスタンスに直接アタッチされている場合、関数は自動的にバインドされません。
>>> a.barFighters
<function barFighters at 0x00A98EF0>
バインドするには、 タイプモジュールの MethodType 関数:
>>> import types
>>> a.barFighters = types.MethodType( barFighters, a )
>>> a.barFighters
<bound method ?.barFighters of <__main__.A instance at 0x00A9BC88>>
>>> a.barFighters()
barFighters
今回は、クラスの他のインスタンスは影響を受けません。
>>> a2.barFighters()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: A instance has no attribute 'barFighters'
他のヒント
モジュール 新しい Python 2.6 以降非推奨となり、3.0 で削除されました。使用してください。 種類
見る http://docs.python.org/library/new.html
以下の例では、意図的に戻り値を削除しています。 patch_me()
関数。戻り値を与えると、patch が新しいオブジェクトを返すと思われるかもしれませんが、そうではありません。受信したオブジェクトを変更します。おそらくこれにより、モンキーパッチのより規律ある使用が容易になるでしょう。
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'>
はじめに - 互換性に関する注意事項:他の回答は Python 2 でのみ機能する可能性があります。この回答は Python 2 と 3 で完全に機能するはずです。Python 3 のみを作成する場合は、からの明示的な継承を省略する可能性があります。 object
, ただし、それ以外の場合、コードは同じままである必要があります。
既存のオブジェクトインスタンスへのメソッドの追加
既存のオブジェクトにメソッドを追加できることを読みました(例:クラス定義には含まれていません) Python では。
そうすることが必ずしも良い決断ではないことは理解しています。 しかし、どうやってこれを行うことができるのでしょうか?
はい、可能ですが、お勧めしません
これはお勧めしません。これは悪い考えです。やめてください。
理由は次のとおりです。
- これを行うすべてのインスタンスにバインドされたオブジェクトを追加します。これを頻繁に行うと、おそらく大量のメモリを浪費することになります。通常、バインドされたメソッドは呼び出しの短期間のみ作成され、その後自動的にガベージ コレクションが行われると存在しなくなります。これを手動で行うと、バインドされたメソッドを参照する名前バインディングが作成され、使用時のガベージ コレクションが防止されます。
- 通常、特定の型のオブジェクト インスタンスには、その型のすべてのオブジェクトに対するメソッドがあります。他の場所にメソッドを追加すると、一部のインスタンスにはそれらのメソッドがあり、他のインスタンスにはそれらのメソッドがありません。プログラマーはこれを期待していないため、規約に違反する危険があります。 最小の驚きの法則.
- これを行わない正当な理由が他にもあるため、これを行うとさらに自分自身の評判が悪くなります。
したがって、よほどの理由がない限り、これを行わないことをお勧めします。 クラス定義で正しいメソッドを定義する方がはるかに良いです。 または 少ない できれば、次のようにクラスに直接モンキーパッチを適用します。
Foo.sample_method = sample_method
ただし、有益なので、これを行ういくつかの方法を紹介します。
どのようにして実現できるのか
ここにいくつかのセットアップコードがあります。クラス定義が必要です。輸入される可能性もありますが、実際には問題ありません。
class Foo(object):
'''An empty class to demonstrate adding a method to an instance'''
インスタンスを作成します。
foo = Foo()
それに追加するメソッドを作成します。
def sample_method(self, bar, baz):
print(bar + baz)
メソッドなし (0) - 記述子メソッドを使用します。 __get__
関数のドット付きルックアップは、 __get__
関数のメソッドをインスタンスに結合し、オブジェクトをメソッドにバインドして、「バインドされたメソッド」を作成します。
foo.sample_method = sample_method.__get__(foo)
そしていま:
>>> foo.sample_method(1,2)
3
メソッド 1 - types.MethodType
まず、型をインポートし、そこからメソッド コンストラクターを取得します。
import types
次に、メソッドをインスタンスに追加します。これを行うには、メソッドから MethodType コンストラクターが必要です。 types
モジュール(上でインポートしたもの)。
types.MethodType の引数のシグネチャは次のとおりです。 (function, instance, class)
:
foo.sample_method = types.MethodType(sample_method, foo, Foo)
そして使用法:
>>> foo.sample_method(1,2)
3
方法 2:字句結合
まず、メソッドをインスタンスにバインドするラッパー関数を作成します。
def bind(instance, method):
def binding_scope_fn(*args, **kwargs):
return method(instance, *args, **kwargs)
return binding_scope_fn
使用法:
>>> foo.sample_method = bind(foo, sample_method)
>>> foo.sample_method(1,2)
3
方法 3:functools.partial
部分関数は、最初の引数 (およびオプションでキーワード引数) を関数に適用し、後で残りの引数 (およびオーバーライドするキーワード引数) を使用して呼び出すことができます。したがって:
>>> from functools import partial
>>> foo.sample_method = partial(sample_method, foo)
>>> foo.sample_method(1,2)
3
バインドされたメソッドがインスタンスの部分的な関数であると考えると、これは理にかなっています。
オブジェクト属性としての非バインド関数 - これが機能しない理由:
クラスに追加するのと同じ方法でsample_methodを追加しようとすると、インスタンスからバインドされておらず、暗黙的なselfを最初の引数として取りません。
>>> 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)
このメソッドは実際には self
引数変数)ですが、他のインスタンスの予期される署名と一致しません(このインスタンスにモンキーパッチを適用している場合)。
>>> foo.sample_method(foo, 1, 2)
3
結論
あなたは今、いくつかの方法を知っています できた これを実行してください。しかし、真剣に考えて、これは実行しないでください。
上記の回答は重要な点を見逃していたと思います。
メソッドを備えたクラスを作成しましょう。
class A(object):
def m(self):
pass
それでは、ipython で遊んでみましょう。
In [2]: A.m
Out[2]: <unbound method A.m>
わかりました、それで m() どういうわけかバインドされていないメソッドになります あ. 。しかし、本当にそうなのでしょうか?
In [5]: A.__dict__['m']
Out[5]: <function m at 0xa66b8b4>
判明したのは、 m() は単なる関数であり、その参照が追加されます あ クラス辞書 - 魔法はありません。それでなんで 午前 バインドされていないメソッドを提供しますか?これは、ドットが単純な辞書検索に変換されないためです。これは事実上、 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
さて、なぜ最後の行が 2 回出力されるのかは頭からはわかりませんが、それでもそこで何が起こっているのかは明らかです。
さて、デフォルトの __getattribute__ が行うことは、属性がいわゆる ディスクリプタ かどうか、つまり特別な __get__ メソッドを実装している場合。そのメソッドを実装している場合、返されるのは、その __get__ メソッドを呼び出した結果です。最初のバージョンに戻ると、 あ クラス、これは私たちが持っているものです:
In [28]: A.__dict__['m'].__get__(None, A)
Out[28]: <unbound method A.m>
また、Python 関数は記述子プロトコルを実装しているため、オブジェクトの代わりに呼び出された場合、関数自体が __get__ メソッドでそのオブジェクトにバインドされます。
では、既存のオブジェクトにメソッドを追加するにはどうすればよいでしょうか?クラスにパッチを当てても構わないと仮定すると、それは次のように簡単です。
B.m = m
それから B.m 記述子マジックのおかげで、バインドされていないメソッドに「なります」。
また、単一のオブジェクトにメソッドを追加したい場合は、types.MethodType を使用して自分で機構をエミュレートする必要があります。
b.m = types.MethodType(m, b)
ところで:
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'>
Python では、モンキー パッチは通常、クラスまたは関数のシグネチャを独自のシグネチャで上書きすることで機能します。以下はからの例です ゾーペ Wiki:
from SomeOtherProduct.SomeModule import SomeClass
def speak(self):
return "ook ook eee eee eee!"
SomeClass.speak = speak
このコードは、クラスの speech と呼ばれるメソッドを上書き/作成します。ジェフ・アトウッドの中で モンキーパッチに関する最近の投稿. 。彼は、私が現在仕事で使用している言語である C# 3.0 の例を示しています。
メソッドをインスタンスにアタッチするには、少なくとも 2 つの方法があります。 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>>
ラムダを使用してメソッドをインスタンスにバインドできます。
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()
出力:
This is instance string
あなたが探しているのは setattr
私は信じている。これを使用してオブジェクトに属性を設定します。
>>> 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>
この質問では Python 以外のバージョンについて質問しているため、JavaScript は次のとおりです。
a.methodname = function () { console.log("Yay, a new method!") }
Jason Pratt とコミュニティ Wiki の回答を統合し、さまざまなバインド方法の結果を確認します。
特にバインディング関数をクラスメソッドとして追加する方法に注意してください。 作品, ですが、参照スコープが間違っています。
#!/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>>
個人的には、イテレータ内でも新しいメソッド名を動的に割り当てることができる外部 ADDMETHOD 関数ルートを好みます。
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>>
君たちは本当に見たほうがいいよ 禁断の果実, 、これは、文字列を含むあらゆる Python クラスへのモンキー パッチのサポートを提供する Python ライブラリです。
これは実際には「Jason Pratt」の回答へのアドオンです。
ジェイソンの答えは機能しますが、クラスに関数を追加したい場合にのみ機能します。.py ソース コード ファイルから既存のメソッドを再ロードしようとしても、うまくいきませんでした。
回避策を見つけるのに何年もかかりましたが、トリックは簡単なようです...1.ソースコードファイルからコードをインポートする2.nd force a reload 3.rd wutes type.functionType(...)インポートされたメソッドとバインドされたメソッドを関数に変換すると、現在のグローバル変数を渡すことができます。リロードされた方法は別の名前空間にあります。
例:
# 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
ジェイソン・プラットが投稿したことは正しいです。
>>> 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'>
ご覧のとおり、Python は b() を a() と何ら異なるものとはみなしません。Python では、すべてのメソッドは関数である単なる変数です。
少しでもお役に立てれば、私は最近、モンキー パッチのプロセスをより便利にするために、Gorilla という名前の Python ライブラリをリリースしました。
関数の使用 needle()
という名前のモジュールにパッチを適用します guineapig
次のようになります。
import gorilla
import guineapig
@gorilla.patch(guineapig)
def needle():
print("awesome")
ただし、次に示すように、より興味深い使用例も処理します。 よくある質問 から ドキュメンテーション.
コードは次の場所で入手できます GitHub.
この質問は何年も前に開かれましたが、デコレータを使用して関数のクラス インスタンスへのバインディングをシミュレートする簡単な方法があります。
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 ()
そこで、関数とインスタンスをバインダー デコレーターに渡すと、最初の関数と同じコード オブジェクトを持つ新しい関数が作成されます。次に、クラスの指定されたインスタンスが、新しく作成された関数の属性に格納されます。デコレーターは、コピーされた関数を自動的に呼び出す (3 番目の) 関数を返し、インスタンスを最初のパラメーターとして渡します。
結論として、クラス インスタンスへのバインドをシミュレートする関数が得られます。元の機能はそのままにします。
上記のすべてのメソッドが追加されたメソッドとインスタンスの間に循環参照を作成し、ガベージ コレクションまでオブジェクトが永続化されることを誰も言及していないのは奇妙に思えます。オブジェクトのクラスを拡張して記述子を追加する古いトリックがありました。
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)) )
これでセルフポインタが使えるようになります