誰かが次のコードの最後の行を完全に説明できますか?
def myMethod(self):
# do something
myMethod = transformMethod(myMethod)
メソッドの定義を別のメソッドに渡したいのはなぜですか?そして、それはどのように機能しますか?前もって感謝します!
メソッドの定義を別のメソッドに渡したいのはなぜですか?
その動作を変更したいからです。
そして、それはどのように機能しますか?
完全に、関数はPythonのファーストクラスであるためです。
def decorator(f):
def wrapper(*args, **kwargs):
print 'Before!'
res = f(*args, **kwargs)
print 'After!'
return res
return wrapper
def somemethod():
print 'During...'
somemethod = decorator(somemethod)
somemethod()
これは、関数のラッピングの例です。これは、関数を引数として受け取り、元の関数の動作を変更する新しい関数を返す関数がある場合です。
これを使用する方法の例を次に示します。これは、呼び出しごとに「Enter」と「Exit」を出力する単純なラッパーです。
def wrapper(func):
def wrapped():
print 'Enter'
result = func()
print 'Exit'
return result
return wrapped
そして、これをどのように使用できるかの例を次に示します。
>>> def say_hello():
... print 'Hello'
...
>>> say_hello() # behavior before wrapping
Hello
>>> say_hello = wrapper(say_hello)
>>> say_hello() # behavior after wrapping
Enter
Hello
Exit
便宜上、Pythonは、関数定義時に同じことを行う関数ラッピングの短縮バージョンであるデコレータ構文を提供します。これを使用する方法は次のとおりです。
>>> @wrapper
... def say_hello():
... print 'Hello'
...
>>> say_hello()
Enter
Hello
Exit
あなたが説明するのはデコレータです。これはメソッド/関数の変更の形式であり、デコレータの特別な構文を使用するとはるかに簡単に実行できます。
あなたが説明することは同等です
@transformMethod
def myMethod(self):
# do something
デコレータは非常に広く使用されており、たとえば、、、などの形式で使用されます@staticmethod
。@classmethod
@functools.wraps()
@contextlib.contextmanager
あるPythonバージョン(2.6だったと思います)なので、クラスもデコレーションできます。
どちらの種類の装飾者も、関数やクラスでさえないオブジェクトを返すことができます。たとえば、ジェネレータ関数をdictやセットなどに変換する方法で装飾できます。
apply = lambda tobeapplied: lambda f: tobeapplied(f())
@apply(dict)
def mydict():
yield 'key1', 'value1'
yield 'key2', 'value2'
print mydict
@apply(set)
def myset():
yield 1
yield 2
yield 1
yield 4
yield 2
yield 7
print myset
私はここで何をしますか?
「適用するもの」を取り、別の関数を返す関数を作成します。
この「内部」関数は、装飾される関数を受け取り、それを呼び出し、その結果を外部関数に入れて、この結果を返します。
f()
dict()
またはに配置されるジェネレータオブジェクトを返しますset()
。
Pythonでは、すべてがオブジェクトであることを理解する必要があります。関数はオブジェクトです。関数オブジェクトでも、他の種類のオブジェクトで実行できるのと同じことを実行できます。リストに格納する、辞書に格納する、関数呼び出しから戻るなどです。
あなたが示したようなコードの通常の理由は、他の関数オブジェクトを「ラップ」することです。たとえば、関数によって返された値を出力するラッパーを次に示します。
def print_wrapper(fn):
def new_wrapped_fn(*args):
x = fn(*args)
print("return value of %s is: %s" % (fn.__name__, repr(x)))
return x
return new_wrapped_fn
def test(a, b):
return a * b
test = print_wrapper(test)
test(2, 3) # prints: return value of test is: 6
これは非常に便利なタスクであり、Pythonが特別にサポートしている、非常に一般的なタスクです。Googleで「Pythonデコレータ」を検索します。
元の質問で、「メソッドの定義を別のメソッドに渡すのはなぜですか?」と質問しました。次に、コメントで、「メソッドの実際のソースコードを変更してみませんか?」と質問しました。実際、これは非常に良い質問であり、手を振らずに答えるのは難しい質問だと思います。デコレータは、コードがある程度複雑になったときにのみ本当に役立つからです。ただし、次の2つの機能を考慮すると、デコレータのポイントがより明確になると思います。
def add_x_to_sequence(x, seq):
result = []
for i in seq:
result.append(i + x)
return result
def subtract_x_from_sequence(x, seq):
result = []
for i in seq:
result.append(i - x)
return result
さて、これらの2つのサンプル関数にはいくつかの欠陥があります。たとえば、実際には、リスト内包表記として書き直すだけですが、今のところ明らかな欠陥を無視して、このように記述しなければならないfor
ふりをしましょう。シーケンスを反復するループとして。私たちは今、2つの機能がほぼ同じことを行い、重要な瞬間にのみ異なるという問題に直面しています。つまり、ここで繰り返しているということです。そしてそれは問題です。今度は、より多くのコード行を維持し、バグが表示される余地を残し、バグが表示されたら非表示にする余地を増やす必要があります。
この問題に対する古典的なアプローチの1つは、次のように、関数を受け取り、それをシーケンス全体に適用する関数を作成することです。
def my_map(func, x, seq):
result = []
for i in seq:
result.append(func(i, x))
return result
今、私たちがしなければならないのは、渡す特定の関数を定義するmy_map
ことだけです(これは実際には組み込みmap
関数の特殊なバージョンです)。
def sub(a, b):
return a - b
def add(a, b):
return a + b
そして、次のように使用できます。
added = my_map(sub, x, seq)
しかし、このアプローチには問題があります。たとえば、元のスタンドアロン関数よりも読みにくいです。また、アイテムのリストに加算または減算するたびにx
、関数と値を引数として指定する必要があります。これを頻繁に行う場合は、常に同じアクションを参照する単一の関数名を使用することをお勧めします。これにより、読みやすさが向上し、コードで何が起こっているのかを理解しやすくなります。上記を別の関数でラップすることができます...
def add_x_to_sequence(x, seq):
return my_map(add, x, seq)
しかし今、私たちは再び自分自身を繰り返しています!また、名前空間が乱雑になり、関数が急増しています。
デコレータは、これらの問題から抜け出す方法を提供します。毎回関数を別の関数に渡す代わりに、一度渡すことができます。まず、ラッパー関数を定義します。
def vectorize(func):
def wrapper(x, seq):
result = []
for i in seq:
result.append(func(i, x))
return result
return wrapper
これで、関数を定義して上記に渡し、ラップするだけです。
def add_x_to_sequence(a, b):
return a + b
add_x_to_sequence = vectorize(add_x_to_sequence)
または、デコレータ構文を使用します。
@vectorize
def add_x_to_sequence(a, b):
return a + b
これで、さまざまなvectorize
d関数を記述でき、それらすべてのfor
ロジックが1か所で実行されます。これで、多くの異なる関数を個別に修正または最適化する必要がなくなりました。ループ関連のバグとループ関連の最適化はすべて同じ場所で発生します。そして、特別に定義された関数の読みやすさの利点をすべて享受できます。