12

実際に各デコレーションに移動してコメントアウトせずに、デコレータのオンとオフを切り替える最良の方法は何ですか?ベンチマークデコレータがあるとします。

# deco.py
def benchmark(func):
  def decorator():
    # fancy benchmarking 
  return decorator

モジュールでは、次のようになります。

# mymodule.py
from deco import benchmark

class foo(object):
  @benchmark
  def f():
    # code

  @benchmark
  def g():
    # more code

それは問題ありませんが、ベンチマークを気にせず、オーバーヘッドを望まない場合があります。私は次のことをしています。別のデコレータを追加します。

# anothermodule.py
def noop(func):
  # do nothing, just return the original function
  return func

次に、インポート行をコメントアウトして、別の行を追加します。

# mymodule.py
#from deco import benchmark 
from anothermodule import noop as benchmark

これで、ベンチマークはファイルごとに切り替えられ、問題のモジュールのインポートステートメントを変更するだけで済みます。個々のデコレータは独立して制御できます。

これを行うためのより良い方法はありますか?ソースファイルをまったく編集する必要がなく、他の場所のどのファイルでどのデコレータを使用するかを指定すると便利です。

4

7 に答える 7

9

デコレータ自体に条件を追加できます。

def benchmark(func):
    if not <config.use_benchmark>:
        return func
    def decorator():
    # fancy benchmarking 
    return decorator
于 2013-01-31T22:33:49.403 に答える
4

私は次のアプローチを使用しています。CaptainMurphyによって提案されたものとほぼ同じですが、関数のようにデコレータを呼び出す必要がないという利点があります。

import functools

class SwitchedDecorator:
    def __init__(self, enabled_func):
        self._enabled = False
        self._enabled_func = enabled_func

    @property
    def enabled(self):
        return self._enabled

    @enabled.setter
    def enabled(self, new_value):
        if not isinstance(new_value, bool):
            raise ValueError("enabled can only be set to a boolean value")
        self._enabled = new_value

    def __call__(self, target):
        if self._enabled:
            return self._enabled_func(target)
        return target


def deco_func(target):
    """This is the actual decorator function.  It's written just like any other decorator."""
    def g(*args,**kwargs):
        print("your function has been wrapped")
        return target(*args,**kwargs)
    functools.update_wrapper(g, target)
    return g


# This is where we wrap our decorator in the SwitchedDecorator class.
my_decorator = SwitchedDecorator(deco_func)

# Now my_decorator functions just like the deco_func decorator,
# EXCEPT that we can turn it on and off.
my_decorator.enabled=True

@my_decorator
def example1():
    print("example1 function")

# we'll now disable my_decorator.  Any subsequent uses will not
# actually decorate the target function.
my_decorator.enabled=False
@my_decorator
def example2():
    print("example2 function")

上記では、example1は装飾され、example2は装飾されません。モジュールごとにデコレータを有効または無効にする必要がある場合、別のコピーが必要なときはいつでも新しいSwitchedDecoratorを作成する関数があります。

于 2015-02-22T04:25:31.683 に答える
1

デコレータaを使用してデコレータbを装飾する必要があると思います。これにより、決定関数を使用してデコレータbのオンとオフを切り替えることができます。

これは複雑に聞こえますが、アイデアはかなり単純です。

それで、あなたがデコレータロガーを持っているとしましょう:

from functools import wraps 
def logger(f):
    @wraps(f)
    def innerdecorator(*args, **kwargs):
        print (args, kwargs)
        res = f(*args, **kwargs)
        print res
        return res
    return innerdecorator

これは非常に退屈なデコレータであり、これら、キャッシャー、ロガー、ものを注入するもの、ベンチマークなどが12個ほどあります。ifステートメントで簡単に拡張できますが、これは悪い選択のようです。それから私はたくさんのデコレータを変更しなければならないので、それはまったく面白くありません。

じゃあ何をすればいいの?1つ上のレベルに進みましょう。デコレータをデコレータできるデコレータがあるとしましょう。このデコレータは次のようになります。

@point_cut_decorator(logger)
def my_oddly_behaving_function

このデコレータはロガーを受け入れますが、これはあまり興味深い事実ではありません。ただし、ロガーをmy_oddly_behaving_functionに適用するかどうかを選択するのに十分な機能もあります。アスペクト指向プログラミングのいくつかの側面があるため、私はそれをpoint_cut_decoratorと呼びました。ポイントカットは、いくつかのコード(アドバイス)を実行フローと織り交ぜる必要がある場所のセットです。ポイントカットの定義は通常、1か所にあります。この手法は非常に似ているようです。

決定ロジックをどのように実装できますか。さて、私はデコレータ、デコレータ、ファイル名前を受け入れる関数を作成することを選択しました。これは、デコレータを適用する必要があるかどうかだけを言うことができます。これらは座標であり、場所を非常に正確に特定するのに十分です。

これはpoint_cut_decoratorの実装です。私は決定関数を単純な関数として実装することを選択しました。これを拡張して、設定または構成から決定させることができます。4つの座標すべてに正規表現を使用すると、非常に何かが発生します。強力:

from functools import wraps

myselectorは決定関数であり、trueの場合、デコレータは適用され、falseの場合は適用されません。パラメータは、ファイル名、モジュール名、装飾されたオブジェクト、そして最後にデコレータです。これにより、きめ細かい方法で動作を切り替えることができます。

def myselector(fname, name, decoratee, decorator):
    print fname

    if decoratee.__name__ == "test" and fname == "decorated.py" and decorator.__name__ == "logger":
        return True
    return False 

これは関数をデコレートし、myselectorをチェックし、myselectorが続行すると言う場合、デコレータを関数に適用します。

def point_cut_decorator(d):
    def innerdecorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            if myselector(__file__, __name__, f, d):
                ps = d(f)
                return ps(*args, **kwargs)
            else:
                return f(*args, **kwargs)
        return wrapper
    return innerdecorator


def logger(f):
    @wraps(f)
    def innerdecorator(*args, **kwargs):
        print (args, kwargs)
        res = f(*args, **kwargs)
        print res
        return res
    return innerdecorator

そして、これはあなたがそれを使う方法です:

@point_cut_decorator(logger)
def test(a):
    print "hello"
    return "world"

test(1)

編集:

これは私が話した正規表現のアプローチです:

from functools import wraps
import re

ご覧のとおり、デコレータを適用するかどうかを決定するいくつかのルールをどこかに指定できます。

rules = [{
    "file": "decorated.py",
    "module": ".*",
    "decoratee": ".*test.*",
    "decorator": "logger"
}]

次に、すべてのルールをループして、ルールが一致する場合はTrueを返し、ルールが一致しない場合はfalseを返します。本番環境でルールを空にすることで、アプリケーションの速度が大幅に低下することはありません。

def myselector(fname, name, decoratee, decorator):
    for rule in rules:
        file_rule, module_rule, decoratee_rule, decorator_rule = rule["file"], rule["module"], rule["decoratee"], rule["decorator"]
        if (
            re.match(file_rule, fname)
            and re.match(module_rule, name)
            and re.match(decoratee_rule, decoratee.__name__)
            and re.match(decorator_rule, decorator.__name__)
        ):
            return True
    return False
于 2013-10-27T20:47:12.687 に答える
0

これが、モジュールごとの切り替えのために私が最終的に思いついたものです。@nneonneoの提案を出発点として使用します。

ランダムモジュールは通常どおりデコレータを使用し、切り替えの知識はありません。

foopkg.py:

from toggledeco import benchmark

@benchmark
def foo():
    print("function in foopkg")

barpkg.py:

from toggledeco import benchmark

@benchmark
def bar():
    print("function in barpkg")

デコレータモジュール自体は、無効にされたすべてのデコレータの関数参照のセットを維持し、各デコレータはこのセットに存在するかどうかをチェックします。その場合、raw関数(デコレータなし)を返すだけです。デフォルトでは、セットは空です(すべてが有効になっています)。

toggledeco.py:

import functools

_disabled = set()
def disable(func):
    _disabled.add(func)
def enable(func):
    _disabled.discard(func)

def benchmark(func):
    if benchmark in _disabled:
        return func
    @functools.wraps(func)
    def deco(*args,**kwargs):
        print("--> benchmarking %s(%s,%s)" % (func.__name__,args,kwargs))
        ret = func(*args,**kwargs)
        print("<-- done")
    return deco

メインプログラムは、インポート中に個々のデコレータのオンとオフを切り替えることができます。

from toggledeco import benchmark, disable, enable

disable(benchmark) # no benchmarks...
import foopkg

enable(benchmark) # until they are enabled again
import barpkg

foopkg.foo() # no benchmarking 
barpkg.bar() # yes benchmarking

reload(foopkg)
foopkg.foo() # now with benchmarking

出力:

function in foopkg
--> benchmarking bar((),{})
function in barpkg
<-- done
--> benchmarking foo((),{})
function in foopkg
<-- done

これには、メイン関数にインポートされたモジュールからインポートされたサブモジュールに有効化/無効化が細流化するバグ/機能が追加されています。

編集

これが@nneonneoによって提案されたクラスです。@benchmark()これを使用するには、デコレータを関数(ではなく)として呼び出す必要があります@benchmark

class benchmark:
    disabled = False

    @classmethod
    def enable(cls):
        cls.disabled = False

    @classmethod
    def disable(cls):
        cls.disabled = True

    def __call__(cls,func):
        if cls.disabled:
            return func
        @functools.wraps(func)
        def deco(*args,**kwargs):
            print("--> benchmarking %s(%s,%s)" % (func.__name__,args,kwargs))
            ret = func(*args,**kwargs)
            print("<-- done")
        return deco
于 2013-03-08T19:55:41.590 に答える
0

デコレータの本体内に設定ファイルのチェックを実装します。設定ファイルに従ってベンチマークを使用する必要がある場合は、現在のデコレータの本体に移動します。そうでない場合は、関数を返し、それ以上何もしません。このフレーバーの何か:

# deco.py
def benchmark(func):
  if config == 'dontUseDecorators': # no use of decorator
      # do nothing
      return func
  def decorator(): # else call decorator
      # fancy benchmarking 
  return decorator

装飾された関数を呼び出すとどうなりますか?@

@benchmark
def f():
    # body comes here

このためのシンタックスシュガーです

f = benchmark(f)

したがって、configでデコレータを見落とすようにしたい場合は、f = f()期待どおりのことをしているだけです。

于 2013-09-30T08:35:33.027 に答える
0

私はまだ誰もこれを提案していないと思います:

benchmark_modules = set('mod1', 'mod2') # Load this from a config file

def benchmark(func):
  if not func.__module__ in benchmark_modules:
      return func

  def decorator():
    # fancy benchmarking 
  return decorator

各関数またはメソッドには__module__、関数が定義されているモジュールの名前である属性があります。ベンチマークを実行するモジュールのホワイトリスト(または必要に応じてブラックリスト)を作成します。そのモジュールをベンチマークしたくない場合は、元の装飾されていない関数を返すだけです。

于 2013-09-30T09:23:10.420 に答える
0

別のまっすぐな方法:

# mymodule.py
from deco import benchmark

class foo(object):

  def f():
    # code

  if <config.use_benchmark>:
    f = benchmark(f)

  def g():
    # more code

  if <config.use_benchmark>:
    g = benchmark(g)
于 2020-01-29T19:15:35.970 に答える