回答:
デコレーターを使用すると、1つの関数が別の関数に置き換えられます。つまり、デコレータがある場合
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
あなたの新しい関数の名前だからです。実際、のdocstringを見ると、docstring がないf
ため空白にwith_logging
なります。そのため、記述した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'
functools.wraps
この仕事が必要なのですか、そもそもデコレータパターンの一部にすぎないのではないでしょうか。@wrapsを使用したくない場合
@wraps
コピーされた値に対してさまざまなタイプの変更または注釈を実行するために、独自のバージョンを実行するいくつかのデコレータを作成しました。基本的に、これはPythonの哲学の延長であり、明示的は暗黙的よりも優れており、特別な場合はルールを破るほど特別ではありません。(@wraps
なんらかの特別なオプトアウトメカニズムを使用するよりも、コードがはるかにシンプルになり、言語を手動で提供する必要がある場合に理解しやすくなります。)
デコレーターには、関数ではなくクラスをよく使用します。オブジェクトは関数に期待されるものと同じ属性をすべて持っているわけではないので、私はこれでいくつかの問題を抱えていました。たとえば、オブジェクトには属性はありません__name__
。Djangoが「オブジェクトに属性 ' __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)
@wraps
言うように、@wraps
はの便利な関数functools.update_wrapper()
です。クラスデコレータの場合update_wrapper()
、__init__()
メソッドから直接呼び出すことができます。だから、あなたが作成する必要はありませんDecBase
あなただけに含めることができる、まったく__init__()
のprocess_login
行:update_wrapper(self, func)
。それで全部です。
Python 3.5以降では:
@functools.wraps(f)
def g():
pass
のエイリアスですg = functools.update_wrapper(g, f)
。それはちょうど3つのことをします:
__module__
、__name__
、__qualname__
、__doc__
、と__annotations__
の属性f
にg
。このデフォルトのリストはにありWRAPPER_ASSIGNMENTS
、functoolsソースで確認できます。__dict__
でg
を更新しますf.__dict__
。(WRAPPER_UPDATES
ソースを参照)__wrapped__=f
属性を設定しますg
結果はg
、と同じ名前、docstring、モジュール名、および署名を持つように見えますf
。唯一の問題は、署名に関してこれが実際には当てはまらないことinspect.signature
です。デフォルトでは、ラッパーチェーンに従います。ドキュメントでinspect.signature(g, follow_wrapped=False)
説明されているように使用して確認できます。これは迷惑な結果をもたらします:
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 egを使用するときはいつでも:@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は単なる通常の関数です。この公式の例を考えてみましょう。助けを借りて、ソースコード、我々は実装し、次のように実行する手順の詳細を見ることができます:
wrapper = O1 .__ call __(ラッパー)
実装チェック__call__を、我々はこの段階、(左側)の後にそれを参照してくださいラッパーオブジェクトがでたなり(* self.args、* argsを、** newkeywords)self.funcの作成チェックO1をして__new__、我々self.funcが関数update_wrapperであることを知ってください。最初のパラメーターとして、パラメーター* args(右側のラッパー)を使用します。update_wrapperの最後のステップを確認すると、右側のラッパーが返され、必要に応じて一部の属性が変更されています。