web-dev-qa-db-ja.com

functools.wrapsは何をしますか?

これに関するコメント 別の質問への答え で、誰かがfunctools.wrapsが何をしていたのかわからないと言った。だから、私はこの質問をして、将来の参照のためにStackOverflowにそれの記録があるようにしています:functools.wrapsは正確に何をしますか?

557
Eli Courtwright

デコレータを使用すると、ある機能を別の機能に置き換えます。言い換えれば、あなたがデコレータを持っているなら

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'
899
Eli Courtwright

私は私のデコレータには関数ではなくクラスをよく使います。オブジェクトは関数に期待されているのと同じ属性をすべて持っているわけではないので、私はこれにいくらか問題を抱えていました。例えば、オブジェクトは属性__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)
20
Josh

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__のすべての要素でgf.__dict__を更新します。 (ソースのWRAPPER_UPDATESを参照)
  • gに新しい__wrapped__=f属性を設定します。

その結果、gfと同じ名前、docstring、モジュール名、およびシグネチャを持つように見えます。唯一の問題は、シグニチャに関してはこれが実際には正しくないことです。それは、inspect.signatureがデフォルトでラッパーチェーンに従うということです。 doc で説明されているようにinspect.signature(g, follow_wrapped=False)を使って確認できます。これは厄介な結果をもたらします。

  • 提供された引数が無効な場合でも、ラッパーコードは実行されます。
  • ラッパーコードは、受け取った* args、** kwargsから、その名前を使用して引数に簡単にアクセスすることはできません。実際、すべてのケース(位置、キーワード、デフォルト)を処理しなければならず、したがってSignature.bind()のようなものを使用する必要があります。

functools.wrapsとデコレータの間には少し混乱があります。デコレータを開発するための非常によくあるユースケースは関数をラップすることだからです。しかし、どちらも完全に独立した概念です。違いを理解することに興味があるなら、私は両方のためにヘルパーライブラリを実装しました: decopatch 簡単にデコレータを書くため、そして makefun@wrapsのシグネチャ保存代替を提供するため。 makefunは、有名なdecoratorライブラリと同じ実績のあるトリックに依存していることに注意してください。

4
smarie

これはラップに関するソースコードです。

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)
3
Baliang
  1. 前提条件:デコレータの使い方と特にラップの使い方を知っておく必要があります。これ コメント は少し明確に説明しています---これ リンク もそれをかなりよく説明しています。

  2. 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()は、簡略化されたバージョン、つまりラッパー関数自体の内部ではなく、ラッパー関数の内部にあるもののオブジェクトを返します。

1
3rdi