this answer to another question へのコメントで、何をしているのかわからないと誰かが言いましたfunctools.wraps
。だから、私はこの質問をしているので、将来の参照のために StackOverflow に記録が残されます:functools.wraps
正確には何をしますか?
7 に答える
デコレータを使用すると、ある関数が別の関数に置き換えられます。つまり、デコレータがある場合
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
は function に置き換えられます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'
私はデコレーターに関数ではなくクラスをよく使用します。オブジェクトが関数に期待されるすべての同じ属性を持っているわけではないため、これで問題が発生しました。たとえば、オブジェクトには属性がありません__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)
- これがあると仮定します: 関数の出力を取り、それを文字列に入れ、その後に 3 つの !!!!. を入れる単純なデコレータ。
def mydeco(func):
def wrapper(*args, **kwargs):
return f'{func(*args, **kwargs)}!!!'
return wrapper
- 2 つの異なる関数を「mydeco」で装飾してみましょう。
@mydeco
def add(a, b):
'''Add two objects together, the long way'''
return a + b
@mydeco
def mysum(*args):
'''Sum any numbers together, the long way'''
total = 0
for one_item in args:
total += one_item
return total
- add(10,20), mysum(1,2,3,4) を実行すると、うまくいきました!
>>> add(10,20)
'30!!!'
>>> mysum(1,2,3,4)
'10!!!!'
- ただし、定義時に関数の名前を与えるname属性は、
>>>add.__name__
'wrapper`
>>>mysum.__name__
'wrapper'
- 悪い
>>> help(add)
Help on function wrapper in module __main__:
wrapper(*args, **kwargs)
>>> help(mysum)
Help on function wrapper in module __main__:
wrapper(*args, **kwargs)
- 次の方法で部分的に修正できます。
def mydeco(func):
def wrapper(*args, **kwargs):
return f'{func(*args, **kwargs)}!!!'
wrapper.__name__ = func.__name__
wrapper.__doc__ = func.__doc__
return wrapper
- ここで、ステップ 5 (2 回目) を再度実行します。
>>> help(add)
Help on function add in module __main__:
add(*args, **kwargs)
Add two objects together, the long way
>>> help(mysum)
Help on function mysum in module __main__:
mysum(*args, **kwargs)
Sum any numbers together, the long way
- functools.wraps (decotator ツール) を使用できます
from functools import wraps
def mydeco(func):
@wraps(func)
def wrapper(*args, *kwargs):
return f'{func(*args, **kwargs)}!!!'
return wrapper
- ステップ5(3回目)をもう一度実行します
>>> help(add)
Help on function add in module main:
add(a, b)
Add two objects together, the long way
>>> help(mysum)
Help on function mysum in module main:
mysum(*args)
Sum any numbers together, the long way