これに関するコメント 別の質問への答え で、誰かがfunctools.wraps
が何をしていたのかわからないと言った。だから、私はこの質問をして、将来の参照のためにStackOverflowにそれの記録があるようにしています:functools.wraps
は正確に何をしますか?
デコレータを使用すると、ある機能を別の機能に置き換えます。言い換えれば、あなたがデコレータを持っているなら
def logged(func):
def with_logging(*args, **kwargs):
print(func.__+ " 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
を取るものとしてリストされるでしょう。
デコレータを使うことが常に関数についてのこの情報を失うことを意味していたなら、それは深刻な問題になるでしょう。だからこそfunctools.wraps
があります。これはデコレータで使われる関数を取り、関数名、docstring、引数リストなどをコピーする機能を追加します。そしてwraps
はそれ自体デコレータなので、次のコードは正しいことをします。
from functools import wraps
def logged(func):
@wraps(func)
def with_logging(*args, **kwargs):
print(func.__+ " 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がエラー「オブジェクトには属性がありません '__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つのことをします:
f
の__module__
、__name__
、__qualname__
、__doc__
、および__annotations__
属性をg
にコピーします。このデフォルトリストはWRAPPER_ASSIGNMENTS
にあります。 functools source に見ることができます。__dict__
のすべての要素でg
のf.__dict__
を更新します。 (ソースのWRAPPER_UPDATES
を参照)g
に新しい__wrapped__=f
属性を設定します。その結果、g
はf
と同じ名前、docstring、モジュール名、およびシグネチャを持つように見えます。唯一の問題は、シグニチャに関してはこれが実際には正しくないことです。それは、inspect.signature
がデフォルトでラッパーチェーンに従うということです。 doc で説明されているように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の後に独自のラッパー関数が続きます。これで与えられた詳細 link に従って、それはそれを言う
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()は、簡略化されたバージョン、つまりラッパー関数自体の内部ではなく、ラッパー関数の内部にあるもののオブジェクトを返します。