2013-01-31 52 views
3

什麼是最佳的方式來打開和關閉裝飾器,而不會實際去每個裝飾和評論出來?假設你有一個標杆裝飾:切換裝飾器

# 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 

現在基準的切換在每個文件的基礎上,只有改變import語句的模塊中有問題。個別裝飾者可以獨立控制。

有沒有更好的方法來做到這一點?根本不需要編輯源文件並指定哪些裝飾器用於其他文件。

回答

4

您可以在有條件加入到裝飾本身:

def benchmark(func): 
    if not <config.use_benchmark>: 
     return func 
    def decorator(): 
    # fancy benchmarking 
    return decorator 
+0

這不允許每個文件切換,並且如果裝飾器來自標準庫或以其他方式無法編輯,則必須在每個裝飾器的包裝中完成。 – engineerC

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") 

的裝飾模塊本身維護了一套已被禁用的所有裝飾功能的引用,每一個裝飾檢查其存在於這一組中。如果是這樣,它只是返回原始函數(沒有裝飾器)。默認情況下,該設置爲空(啓用所有功能)。

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 

這具有增加的錯誤/功能啓用/禁用將逐漸下降到從ma中導入的模塊導入的任何子模塊功能。

編輯

這裏的課由@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 
+0

有趣的方法。我會更進一步,並使用'enable()'和'disable()'方法使裝飾器成爲完整的類。一個'__call __()'方法允許它像一個裝飾器一樣工作,儘管只有一個狀態。這將消除'enable'和'disable'函數,並且需要有一個全局'_disabled'集合,因爲每個單獨的裝飾器都將保持該狀態。 – nneonneo

+0

添加到答案。 – engineerC

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) 

語法糖,所以如果配置要你忽略的裝飾,你只是在做f = f()這是你所期望的。

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__屬性,它是函數定義模塊的名稱。創建要進行基準測試的模塊的白名單(或者黑名單)(如果您願意的話),如果您不想對該模塊進行基準測試,只需返回原始的未修飾函數即可。

0

我認爲你應該使用裝飾器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 

這是一個很無聊的裝飾,我有十幾這些,cachers,記錄器,東西注入的東西,標杆等我可以很容易用if語句擴展它,但這似乎是一個不好的選擇;因爲那時我必須更換十幾個裝飾器,這根本不好玩。

所以,我們要做什麼呢?讓我們走高一級。假設我們有一個裝飾器,可以裝飾裝飾器?這個裝飾器看起來像這樣:

@point_cut_decorator(logger) 
def my_oddly_behaving_function 

這個裝飾器接受記錄器,這不是一個非常有趣的事實。但是它也有足夠的能力來選擇記錄器是否應用於my_oddly_behaving_function。我將它稱爲point_cut_decorator,因爲它具有面向方面編程的某些方面。減分是一組位置,其中一些代碼(建議)必須與執行流程交織在一起。點削減的定義通常在一個地方。這種技術似乎非常相似。

我們該如何實現它的決策邏輯。那麼我選擇了一個函數,它接受裝飾器,裝飾器,文件名稱,它只能說如果一個裝飾器應該適用或不適用。這些座標是非常精確的,可以精確定位位置。

這是point_cut_decorator的實現,我選擇將決定函數實現爲一個簡單的函數,可以將它擴展爲讓它根據您的設置或配置決定,如果您對所有4個座標使用正則表達式,則會結束up:

from functools import wraps 

myselector是決定函數,對於真實的裝飾器應用於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如果規則匹配或假,如果規則不匹配。通過制定規則在生產空,這會不會你的應用程序減慢太多:

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 
2

我一直在使用下面的方法。它與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。