121

非常に一般的なことを行うデコレータを作成したとします。たとえば、すべての引数を特定の型に変換したり、ロギングを実行したり、メモ化を実装したりできます。

次に例を示します。

def args_as_ints(f):
    def g(*args, **kwargs):
        args = [int(x) for x in args]
        kwargs = dict((k, int(v)) for k, v in kwargs.items())
        return f(*args, **kwargs)
    return g

@args_as_ints
def funny_function(x, y, z=3):
    """Computes x*y + 2*z"""
    return x*y + 2*z

>>> funny_function("3", 4.0, z="5")
22

これまでのところすべて順調です。ただし、問題が 1 つあります。装飾された関数は、元の関数のドキュメントを保持しません。

>>> help(funny_function)
Help on function g in module __main__:

g(*args, **kwargs)

幸いなことに、回避策があります。

def args_as_ints(f):
    def g(*args, **kwargs):
        args = [int(x) for x in args]
        kwargs = dict((k, int(v)) for k, v in kwargs.items())
        return f(*args, **kwargs)
    g.__name__ = f.__name__
    g.__doc__ = f.__doc__
    return g

@args_as_ints
def funny_function(x, y, z=3):
    """Computes x*y + 2*z"""
    return x*y + 2*z

今回は、関数名とドキュメントは正しいです。

>>> help(funny_function)
Help on function funny_function in module __main__:

funny_function(*args, **kwargs)
    Computes x*y + 2*z

しかし、まだ問題があります: 関数の署名が間違っています。「*args, **kwargs」という情報はほとんど役に立ちません。

何をすべきか?2 つの単純だが欠陥のある回避策を考えることができます。

1 -- docstring に正しい署名を含めます。

def funny_function(x, y, z=3):
    """funny_function(x, y, z=3) -- computes x*y + 2*z"""
    return x*y + 2*z

これは重複のために悪いです。署名は、自動生成されたドキュメントではまだ正しく表示されません。関数を更新して docstring の変更を忘れたり、タイプミスをしたりするのは簡単です。[そして、はい、docstring が既に関数本体を複製しているという事実を認識しています。これは無視してください。funny_function は単なるランダムな例です。]

2 -- デコレータを使用しないか、特定のシグネチャごとに専用のデコレータを使用します。

def funny_functions_decorator(f):
    def g(x, y, z=3):
        return f(int(x), int(y), z=int(z))
    g.__name__ = f.__name__
    g.__doc__ = f.__doc__
    return g

これは、同一のシグネチャを持つ一連の関数に対してはうまく機能しますが、一般的には役に立ちません。冒頭で述べたように、デコレータを完全に汎用的に使用できるようにしたいと考えています。

完全に一般的で自動のソリューションを探しています。

問題は、装飾された関数シグネチャを作成後に編集する方法はありますか?

それ以外の場合、装飾された関数を構築するときに、関数シグネチャを抽出し、「* kwargs、** kwargs」の代わりにその情報を使用するデコレータを作成できますか? その情報を抽出するにはどうすればよいですか? 装飾された関数をどのように構築すればよいですか -- exec を使用して?

他のアプローチはありますか?

4

8 に答える 8

90
  1. デコレータモジュールをインストールします。

    $ pip install decorator
    
  2. の定義を適応させるargs_as_ints():

    import decorator
    
    @decorator.decorator
    def args_as_ints(f, *args, **kwargs):
        args = [int(x) for x in args]
        kwargs = dict((k, int(v)) for k, v in kwargs.items())
        return f(*args, **kwargs)
    
    @args_as_ints
    def funny_function(x, y, z=3):
        """Computes x*y + 2*z"""
        return x*y + 2*z
    
    print funny_function("3", 4.0, z="5")
    # 22
    help(funny_function)
    # Help on function funny_function in module __main__:
    # 
    # funny_function(x, y, z=3)
    #     Computes x*y + 2*z
    

Python 3.4+

functools.wraps()from stdlibは Python 3.4 以降の署名を保持します:

import functools


def args_as_ints(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        args = [int(x) for x in args]
        kwargs = dict((k, int(v)) for k, v in kwargs.items())
        return func(*args, **kwargs)
    return wrapper


@args_as_ints
def funny_function(x, y, z=3):
    """Computes x*y + 2*z"""
    return x*y + 2*z


print(funny_function("3", 4.0, z="5"))
# 22
help(funny_function)
# Help on function funny_function in module __main__:
#
# funny_function(x, y, z=3)
#     Computes x*y + 2*z

functools.wraps()少なくとも Python 2.5 以降で使用できますが、署名は保持されません。

help(funny_function)
# Help on function funny_function in module __main__:
#
# funny_function(*args, **kwargs)
#    Computes x*y + 2*z

注意:*args, **kwargsの代わりにx, y, z=3.

于 2008-09-29T08:08:30.187 に答える
9

使用できるデコレータを備えたデコレータモジュールがあります:decorator

@decorator
def args_as_ints(f, *args, **kwargs):
    args = [int(x) for x in args]
    kwargs = dict((k, int(v)) for k, v in kwargs.items())
    return f(*args, **kwargs)

次に、メソッドの署名とヘルプが保持されます。

>>> help(funny_function)
Help on function funny_function in module __main__:

funny_function(x, y, z=3)
    Computes x*y + 2*z

編集: JF Sebastian は、関数を変更していないことを指摘しましたargs_as_ints。現在は修正されています。

于 2008-09-29T07:49:23.960 に答える
8

デコレータモジュール、特にこの問題を解決するデコレータデコレータを見てください。

于 2008-09-29T07:43:19.747 に答える
6

2 番目のオプション:

  1. wrapt モジュールをインストールします。

$ easy_install ラップ

wrapt にはボーナスがあり、クラス シグネチャを保持します。


import wrapt
import inspect

@wrapt.decorator def args_as_ints(wrapped, instance, args, kwargs): if instance is None: if inspect.isclass(wrapped): # Decorator was applied to a class. return wrapped(*args, **kwargs) else: # Decorator was applied to a function or staticmethod. return wrapped(*args, **kwargs) else: if inspect.isclass(instance): # Decorator was applied to a classmethod. return wrapped(*args, **kwargs) else: # Decorator was applied to an instancemethod. return wrapped(*args, **kwargs) @args_as_ints def funny_function(x, y, z=3): """Computes x*y + 2*z""" return x * y + 2 * z >>> funny_function(3, 4, z=5)) # 22 >>> help(funny_function) Help on function funny_function in module __main__: funny_function(x, y, z=3) Computes x*y + 2*z
于 2014-08-28T18:15:30.613 に答える