[英]Python decorator optional argument
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'
鑒於此示例代碼,我將如何執行@logged(variable)
?
我試過這個
from functools import wraps
def logged(func):
def outer(var):
@wraps(func)
def with_logging(*args, **kwargs):
print func.__name__ + " was called"
return func(*args, **kwargs)
return with_logging
return outer
我希望像這樣執行:logged(func)(session_variable)
但不起作用。 任何的想法? 我希望能夠做到@logged 和@logged(var)(甚至@logged(var1, var2))謝謝。
這里的訣竅是,你必須反省你得到的東西:
def logged(*setting_args, **setting_kwargs):
no_args = False
if len(setting_args) == 1 \
and not setting_kwargs \
and callable(setting_args[0]):
# We were called without args
func = setting_args[0]
no_args = True
def outer(func):
@wraps(func)
def with_logging(*args, **kwargs):
print "{} was called".format(func.__name__)
print "Setting args are: {}".format(setting_args)
print "Setting keyword args are: {}".format(setting_kwargs)
return func(*args, **kwargs)
return with_logging
if no_args:
return outer(func)
else:
return outer
這將適用於以下任何一項:
# No arguments
@logged
def some_function(x):
pass
# One or more arguments
@logged(1, 2, 3)
def some_function(x):
pass
# One or more keyword arguments
@logged(key=1, another_key=2)
def some_function(x):
pass
# A mix of the two
@logged(1, 2, key=3)
def some_function(x):
pass
如果只使用一個可調用參數調用它,它將不起作用:
# This will break.
@logged(lambda: "Just for fun")
def some_function(x):
pass
無法區分單個可調用設置和裝飾器的無參數調用之間的區別。 但是,如果您需要,您可以傳遞垃圾關鍵字 arg 來解決這個問題:
# This gets around the above limitation
@logged(lambda: "Just for fun", ignored=True)
def some_function(x):
pass
這個問題6年多了,已經有了答案。 我遇到了同樣的情況 - 不得不更新代碼中很多地方使用的裝飾器,並想添加一個可選參數。
我能夠使用不同的方法來完成它 - 來自Python CookBook 3rd Edition ,第 9 章 - 9.6 一書。 定義一個帶有可選參數的裝飾器。 它提出問題,提出解決方案並以討論結束(很棒)。
解決方案:對於Python 3.3+
from functools import wraps, partial
def logged(func=None, *, var1=None, var2=None):
if func is None:
return partial(logged, var1=var1, var2=var2)
@wraps(func)
def with_logging(*args, **kwargs):
print func.__name__ + " was called"
return func(*args, **kwargs)
return with_logging
使用上述方法,您可以執行以下任一操作:
@logged
def f(x):
@logger(var1)
def f(x):
@logger(var1, var2)
def f(x)
說明(最好在書上找)
要了解代碼的工作原理,您需要對裝飾器如何應用於函數及其調用約定有一個深入的了解。
1. 簡單的裝飾器,例如:
# Example use
@logged
def add(x, y):
return x + y
調用順序如下:
def add(x, y):
return x + y
add = logged(add)
在這種情況下,要包裝的函數只是作為第一個參數傳遞給日志記錄。 因此,在解決方案中,logged() 的第一個參數是被包裝的函數。 所有其他參數都必須具有默認值。
2. 裝飾器取參數:
@logged(level=logging.CRITICAL, name='example')
def spam():
print('Spam!')
調用順序如下:
def spam():
print('Spam!')
spam = logged(level=logging.CRITICAL, name='example')(spam)
上面的最后一行,是如何調用帶參數的裝飾器,即在首次調用logged()
,未傳遞要裝飾的函數spam()
,因此我們在裝飾器中將其設置為可選,即在logged
定義中func=None
。 因此,在第一次調用中只傳遞參數。
這反過來又強制其他參數由關鍵字指定。 此外,當傳遞參數時,裝飾器應該返回一個接受函數並包裝它的函數(參見 9.5 節)。 為此,該解決方案使用了一個巧妙的技巧,涉及
functools.partial
。 具體來說,它只是返回自身的部分應用版本,其中除了要包裝的函數外,所有參數都是固定的。
將def outer(var)
向外放,即
def outer(var):
def logged(func):
...
,然后將@outer(somevar)
用於您的函數,這將起作用。
您嘗試的代碼中存在小錯誤。 而不是創建帶有參數的嵌套函數func
> var
> *args, **kwargs
,順序應該是var
> func
> *args, **kwargs
。
以下是滿足您要求的代碼片段。
from functools import wraps
def logged(var=None):
def outer(func):
@wraps(func)
def with_logging(*args, **kwargs):
print func.__name__ + " was called"
return func(*args, **kwargs)
return with_logging
return outer
你可以稱這個裝飾器為:
@logged
def func1():
...
或者,
@logged(xyz)
def func2():
...
要了解有關裝飾器如何工作的更多信息,請參閱帶有可選參數的裝飾器一文。
Sean Vieira 的一個可能的替代解決方案可能是:
from functools import wraps
import inspect
def decorator_defaults(**defined_defaults):
def decorator(f):
args_names = inspect.getargspec(f)[0]
def wrapper(*new_args, **new_kwargs):
defaults = dict(defined_defaults, **new_kwargs)
if len(new_args) == 0:
return f(**defaults)
elif len(new_args) == 1 and callable(new_args[0]):
return f(**defaults)(new_args[0])
else:
too_many_args = False
if len(new_args) > len(args_names):
too_many_args = True
else:
for i in range(len(new_args)):
arg = new_args[i]
arg_name = args_names[i]
defaults[arg_name] = arg
if len(defaults) > len(args_names):
too_many_args = True
if not too_many_args:
final_defaults = []
for name in args_names:
final_defaults.append(defaults[name])
return f(*final_defaults)
if too_many_args:
raise TypeError("{0}() takes {1} argument(s) "
"but {2} were given".
format(f.__name__,
len(args_names),
len(defaults)))
return wrapper
return decorator
@decorator_defaults(start_val="-=[", end_val="]=-")
def my_text_decorator(start_val, end_val):
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
return "".join([f.__name__, ' ', start_val,
f(*args, **kwargs), end_val])
return wrapper
return decorator
@decorator_defaults(end_val="]=-")
def my_text_decorator2(start_val, end_val):
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
return "".join([f.__name__, ' ', start_val,
f(*args, **kwargs), end_val])
return wrapper
return decorator
@my_text_decorator
def func1a(value):
return value
@my_text_decorator()
def func2a(value):
return value
@my_text_decorator2("-=[")
def func2b(value):
return value
@my_text_decorator(end_val=" ...")
def func3a(value):
return value
@my_text_decorator2("-=[", end_val=" ...")
def func3b(value):
return value
@my_text_decorator("|> ", " <|")
def func4a(value):
return value
@my_text_decorator2("|> ", " <|")
def func4b(value):
return value
@my_text_decorator(end_val=" ...", start_val="|> ")
def func5a(value):
return value
@my_text_decorator2("|> ", end_val=" ...")
def func5b(value):
return value
print(func1a('My sample text')) # func1a -=[My sample text]=-
print(func2a('My sample text')) # func2a -=[My sample text]=-
print(func2b('My sample text')) # func2b -=[My sample text]=-
print(func3a('My sample text')) # func3a -=[My sample text ...
print(func3b('My sample text')) # func3b -=[My sample text ...
print(func4a('My sample text')) # func4a |> My sample text <|
print(func4b('My sample text')) # func4b |> My sample text <|
print(func5a('My sample text')) # func5a |> My sample text ...
print(func5b('My sample text')) # func5b |> My sample text ...
注意:它有同樣的缺點,你不能將 1 個參數作為函數傳遞給裝飾器,但是如果你想在多個裝飾器上使用這個功能,你可以避開代碼樣板。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.