functools.wrapsは何をしますか?
質問
このコメント別の質問に答えて、誰かが functools.wraps
が何をしていたのかわからないと言った。したがって、今後の参照のためにStackOverflowに記録するようにこの質問をしています: functools.wraps
は正確に何をしますか?
解決
デコレータを使用すると、ある関数を別の関数に置き換えます。言い換えれば、デコレータを持っている場合
def logged(func):
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
そしてあなたが言うとき
@logged
def f(x):
"""does some math"""
return x + x * x
それは言うこととまったく同じです
def f(x):
"""does some math"""
return x + x * x
f = logged(f)
および関数 f
は関数with_loggingに置き換えられます。残念ながら、これはあなたが言うなら
print(f.__name__)
それは with_logging
を印刷します。これは新しい関数の名前だからです。実際、 f
のdocstringを見ると、 with_logging
にはdocstringがないため空白になり、作成したdocstringはもう存在しません。また、その関数のpydocの結果を見ると、1つの引数 x
を取るものとしてリストされません。代わりに、 * args
および ** kwargs
を使用するものとしてリストされます。これはwith_loggingが使用するものだからです。
デコレータの使用が常に関数に関するこの情報を失うことを意味する場合、それは深刻な問題になります。それが functools.wraps
がある理由です。これは、デコレータで使用される関数を受け取り、関数名、docstring、引数リストなどをコピーする機能を追加します。また、 wraps
自体がデコレータなので、次のコードは正しいことを行います。
from functools import wraps
def logged(func):
@wraps(func)
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
@logged
def f(x):
"""does some math"""
return x + x * x
print(f.__name__) # prints 'f'
print(f.__doc__) # prints 'does some math'
他のヒント
デコレータには、関数ではなくクラスを頻繁に使用します。オブジェクトは関数に期待されるものと同じ属性をすべてもたないため、これで問題が発生していました。たとえば、オブジェクトには属性 __ name __
はありません。 Djangoがエラー" object has no attribute ' __ name __
'"を報告していた場所を追跡するのは非常に困難でした。残念ながら、クラススタイルのデコレータの場合、@ wrapがその役割を果たすとは思いません。代わりに、次のようなベースデコレータクラスを作成しました。
class DecBase(object):
func = None
def __init__(self, func):
self.__func = func
def __getattribute__(self, name):
if name == "func":
return super(DecBase, self).__getattribute__(name)
return self.func.__getattribute__(name)
def __setattr__(self, name, value):
if name == "func":
return super(DecBase, self).__setattr__(name, value)
return self.func.__setattr__(name, value)
このクラスは、装飾されている関数へのすべての属性呼び出しをプロキシします。したがって、次のように2つの引数が指定されていることを確認する単純なデコレータを作成できます。
class process_login(DecBase):
def __call__(self, *args):
if len(args) != 2:
raise Exception("You can only specify two arguments")
return self.func(*args)
Python 3.5以降:
@functools.wraps(f)
def g():
pass
g = functools.update_wrapper(g、f)
のエイリアスです。正確に3つのことを行います:
-
__ module __
、__ name __
、__ qualname __
、__ doc __
、および__ annotations __
g
のf
の属性。このデフォルトのリストはWRAPPER_ASSIGNMENTS
にあり、 functoolsソース。 -
f .__ dict __
のすべての要素でg
の__ dict __
を更新します。 (ソースのWRAPPER_UPDATES
を参照) - 新しい
__ wrapped __ = f
属性をg
に設定します
その結果、 g
は f
と同じ名前、docstring、モジュール名、および署名を持つように見えます。唯一の問題は、署名に関して、これは実際には真実ではないということです。それは、 inspect.signature
がデフォルトでラッパーチェーンに従うということです。 doc 。これは迷惑な結果をもたらします:
- 指定された引数が無効な場合でも、ラッパーコードが実行されます。
- ラッパーコードは、受け取った* args、** kwargsから、名前を使用して引数に簡単にアクセスできません。実際、すべてのケース(位置、キーワード、デフォルト)を処理する必要があるため、
Signature.bind()
のようなものを使用する必要があります。
functools.wraps
とデコレータの間には少し混乱があります。デコレータを開発するための非常に頻繁な使用例は、関数をラップすることです。しかし、どちらも完全に独立した概念です。違いを理解することに興味がある場合は、両方のヘルパーライブラリを実装しました: decopatch デコレータを簡単に作成し、 makefun を使用して、 @wraps コード>。
makefun
は、有名な decorator
ライブラリと同じ実証済みのトリックに依存していることに注意してください。
これはラップに関するソースコードです:
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')
WRAPPER_UPDATES = ('__dict__',)
def update_wrapper(wrapper,
wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
"""Update a wrapper function to look like the wrapped function
wrapper is the function to be updated
wrapped is the original function
assigned is a tuple naming the attributes assigned directly
from the wrapped function to the wrapper function (defaults to
functools.WRAPPER_ASSIGNMENTS)
updated is a tuple naming the attributes of the wrapper that
are updated with the corresponding attribute from the wrapped
function (defaults to functools.WRAPPER_UPDATES)
"""
for attr in assigned:
setattr(wrapper, attr, getattr(wrapped, attr))
for attr in updated:
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
# Return the wrapper so this can be used as a decorator via partial()
return wrapper
def wraps(wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
"""Decorator factory to apply update_wrapper() to a wrapper function
Returns a decorator that invokes update_wrapper() with the decorated
function as the wrapper argument and the arguments to wraps() as the
remaining arguments. Default arguments are as for update_wrapper().
This is a convenience function to simplify applying partial() to
update_wrapper().
"""
return partial(update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)
-
前提条件:デコレータの使用方法、特にラップの使用方法を知っている必要があります。このコメントで少し明確に説明されているか、このリンクも説明しています。
-
Forを使用するときはいつでも:例:@wrapsの後に独自のラッパー関数が続きます。このリンクに記載されている詳細に従って、
functools.wrapsは、ラッパー関数を定義するときに、関数デコレーターとしてupdate_wrapper()を呼び出すための便利な関数です。
partial(update_wrapper、wrapped = wrapped、assigned = assigned、updated = updated)と同等です。
したがって、@ wrapsデコレータは実際にfunctools.partial(func [、* args] [、** keywords])を呼び出します。
functools.partial()の定義には、次のように書かれています
partial()は、関数の引数やキーワードの一部を「フリーズ」して、単純化された署名を持つ新しいオブジェクトを生成する部分関数アプリケーションに使用されます。たとえば、partial()を使用して、ベース引数がデフォルトの2であるint()関数のように動作する呼び出し可能オブジェクトを作成できます。
>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18
これは、@ wrapsがpartial()を呼び出して、ラッパー関数をパラメーターとして渡すという結論に至ります。最後のpartial()は、簡易バージョン、つまりラッパー関数自体ではなく、ラッパー関数の内部にあるオブジェクトを返します。
要するに、 functools.wraps は単なる通常の関数です。 この公式例について考えてみましょう。 ソースコードの助けを借りて、実装および実行手順の詳細については、次を参照してください。
- wraps(f)は、オブジェクト、たとえば O1 を返します。 クラスのオブジェクトです>
- 次のステップは @ O1 ... です。これは、Pythonのデコレータ表記です。つまり
wrapper = O1 .__ call __(wrapper)
__ call __ 、このステップの後、(左側) wrapper は self.func(* self.args、* args、** newkeywordsによって生成されたオブジェクトになります) __ new __ で O1 の作成を確認すると、 self.func が update_wrapper 関数であることがわかります。最初のパラメーターとして、パラメーター * args 、右側のラッパーを使用します。 update_wrapper の最後のステップを確認すると、右側の wrapper が返され、必要に応じて一部の属性が変更されていることがわかります。