質問

このコメント別の質問に答えて、誰かが 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)
  1. 前提条件:デコレータの使用方法、特にラップの使用方法を知っている必要があります。このコメントで少し明確に説明されているか、このリンクも説明しています。

  2. 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 は単なる通常の関数です。 この公式例について考えてみましょう。 ソースコードの助けを借りて、実装および実行手順の詳細については、次を参照してください。

  1. wraps(f)は、オブジェクト、たとえば O1 を返します。 クラスのオブジェクトです>
  2. 次のステップは @ O1 ... です。これは、Pythonのデコレータ表記です。つまり
  

wrapper = O1 .__ call __(wrapper)

__ call __ 、このステップの後、(左側) wrapper self.func(* self.args、* args、** newkeywordsによって生成されたオブジェクトになります) __ new __ O1 の作成を確認すると、 self.func update_wrapper 関数であることがわかります。最初のパラメーターとして、パラメーター * args 、右側のラッパーを使用します。 update_wrapper の最後のステップを確認すると、右側の wrapper が返され、必要に応じて一部の属性が変更されていることがわかります。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top