既存のオブジェクト インスタンスへのメソッドの追加

python oop methods monkeypatching


Pythonでは既存のオブジェクトにメソッドを追加することができると読んだことがあるのですが(クラス定義にはないですが)、その場合はどうすればいいのでしょうか?

そうすることが良いことばかりではないのは理解しています。しかし、どのようにすればいいのでしょうか?




Answer 1 Jason Pratt


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

バウンドされたメソッドはインスタンスに "バインド "されており(どのように記述されているか)、メソッドが呼び出されるたびにそのインスタンスが第一引数として渡されます。

しかし、クラスの属性(インスタンスではなく)である callables はまだバインドされていないので、クラス定義をいつでも変更することができます。

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

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




Answer 2 Evgeny


新しいモジュールは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'>



Answer 3 Aaron Hall


序文-互換性に関する注記:他の回答はPython 2でのみ機能する可能性があります-この回答はPython 2および3で完全に機能するはずです。Python3のみを作成する場合は、 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__ 検索は、インスタンスを使用して関数の__get__メソッドを呼び出し、オブジェクトをメソッドにバインドして、「バインドされたメソッド」を作成します。

foo.sample_method = sample_method.__get__(foo)

そして今。

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

メソッド 1-types.MethodType

まず、型をインポートして、そこからメソッドのコンストラクタを取得します。

import types

次に、メソッドをインスタンスに追加します。これを行うには、 types モジュール(上記でインポートしたもの)のMethodTypeコンストラクターが必要です。

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

usage:

>>> 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 引数変数を使用しないため)バインドされていない関数を機能させることができますが、他のインスタンスの予期されるシグネチャと一致しません(monkey-このインスタンスにパッチを適用する):

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

Conclusion

あなたはこれを行うことができるいくつかの方法を知っていますが、真剣に-これを行わないでください。




Answer 4 Tomasz Zieliński


上記の回答は要点を見逃していたと思います。

メソッドを持ったクラスを作ってみましょう。

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

では、ipythonで遊んでみましょう。

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

さて、m()はどういうわけかAのバインドされていないメソッドになります。しかし、それは本当にそうですか?

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

これは、ことが判明メートル()がに追加されているだけの機能、リファレンスである魔法はありません-クラスの辞書。では、なぜAmは非拘束メソッドを提供するのでしょうか。これは、ドットが単純な辞書検索に変換されないためです。これは事実上、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__メソッドを呼び出した結果です。Aクラスの最初のバージョンに戻ると、次のようになります。

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

そして、Pythonの関数はディスクリプタプロトコルを実装しているので、もしオブジェクトの代わりに呼ばれた場合は、そのオブジェクトの__get__メソッドで自分自身をそのオブジェクトにバインドします。

さて、どのように既存のオブジェクトにメソッドを追加しますか?あなたがクラスのパッチを気にしないと仮定すると、それは次のように簡単です。

B.m = m

次に、Bmは、記述子マジックのおかげで、バインドされていないメソッドになります。

そして、単一のオブジェクトだけにメソッドを追加したい場合は、type.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'>



Answer 5 John Downey


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