18

次のような単純な関数を考えてみましょう

def increment(self):
    self.count += 1

これは Cython を介して実行され、拡張モジュールにコンパイルされます。この関数をクラスのメソッドにしたいとします。例えば:

class Counter:
    def __init__(self):
        self.count = 0

from compiled_extension import increment
Counter.increment = increment

C レベルでの呼び出し規則が壊れるため、これは機能しません。例えば:

>>> c = Counter()
>>> c.increment()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: increment() takes exactly one argument (0 given)

しかし、Python 2 では、次のようにして関数をバインドされていないメソッドに変換できます。

Counter.increment = types.MethodType(increment, None, Counter)

Python 3でこれと同じことをどのように達成できますか?

簡単な方法の 1 つは、スリム ラッパーを使用することです。

from functools import wraps
def method_wraper(f):
    def wrapper(*args, **kwargs):
        return f(*args, **kwargs)
    return wraps(f)(wrapper)

Counter.increment = method_wrapper(increment)

それを行うためのより効率的な方法はありますか?

4

2 に答える 2

5

まず、名前を正しく取得する必要があります。

>>> def increment(obj):
...     obj.count += 1
...
>>> class A(object):
...     def __init__(self):
...         self.count = 0
...
>>> o = A()
>>> o.__init__
<bound method A.__init__ of <__main__.A object at 0x0000000002766EF0>>
>>> increment
<function increment at 0x00000000027797C8>

したがって、適切な名前は関数バインドされたメソッドです。これで、バインドされていないメソッドをバインドする方法を探すことができ、おそらく記述子について読むことになるでしょう:

一般に、記述子は「バインディング動作」を持つオブジェクト属性であり、その属性アクセスは記述子プロトコルのメソッドによってオーバーライドされます。それらのメソッドは、、、__get__および__set__です __delete__。これらのメソッドのいずれかがオブジェクトに対して定義されている場合、それは記述子と呼ばれます。

の異なる呼び出しを使用するだけで、関数をメソッドに簡単に変換できます__get__

>>> increment.__get__(None, type(None))
<function increment at 0x00000000027797C8>
>>> increment.__get__(o, type(o))
<bound method A.increment of <__main__.A object at 0x00000000027669B0>>

そして、それは魅力のように機能します:

>>> o = A()
>>> increment.__get__(None, type(None))(o)
>>> o.count
1
>>> increment.__get__(o, type(o))()
>>> o.count
2

これらの新しくバインドされたメソッドをオブジェクトに簡単に追加できます。

def increment(obj):
    obj.count += 1

def addition(obj, number):
    obj.count += number

class A(object):
    def __init__(self):
        self.count = 0

o = A()
o.inc = increment.__get__(o)
o.add = addition.__get__(o)
print(o.count) # 0
o.inc()
print(o.count) # 1
o.add(5)
print(o.count) # 6

または、関数バインドされたメソッドに変換する独自の記述子を作成します。

class BoundMethod(object):
    def __init__(self, function):
        self.function = function

    def __get__(self, obj, objtype=None):
        print('Getting', obj, objtype)
        return self.function.__get__(obj, objtype)

class B(object):
    def __init__(self):
        self.count = 0

    inc = BoundMethod(increment)
    add = BoundMethod(addition)


o = B()
print(o.count) # 0
o.inc()
# Getting <__main__.B object at 0x0000000002677978> <class '__main__.B'>
print(o.count) # 1
o.add(5) 
# Getting <__main__.B object at 0x0000000002677978> <class '__main__.B'>
print(o.count) # 6

また、これは関数/バインドされたメソッドの原則とうまく一致していることもわかります。

クラス ディクショナリは、メソッドを関数として格納します。クラス定義では、関数を作成するための通常のツールである def と lambda を使用してメソッドが記述されます。通常の関数との唯一の違いは、最初の引数がオブジェクト インスタンス用に予約されていることです。Python の慣例により、インスタンス参照は self と呼ばれますが、this または他の変数名と呼ばれることもあります。

メソッド呼び出しをサポートするために、関数には、__get__()属性アクセス中にメソッドをバインドするためのメソッドが含まれています。これは、すべての関数が、オブジェクトまたはクラスのどちらから呼び出されたかに応じて、バインドされたメソッドまたはバインドされていないメソッドを返す非データ記述子であることを意味します。

そして、関数はインスタンスの初期化中にバインドされたメソッドになります:

>>> B.add
# Getting None <class '__main__.B'>
<function addition at 0x00000000025859C8>
>>> o.add
# Getting <__main__.B object at 0x00000000030B1128> <class '__main__.B'>
<bound method B.addition of <__main__.B object at 0x00000000030B1128>>
于 2014-12-17T10:44:13.963 に答える
0

次のように拡張機能をインポートします。

import compiled_extension

クラスで次のように記述します。

def increment: return compiled_extension.increment()

これはより読みやすく、より効率的かもしれません。

于 2014-07-17T20:47:24.983 に答える