扩展功能上下文的方法?例如在装饰器中
Posted
技术标签:
【中文标题】扩展功能上下文的方法?例如在装饰器中【英文标题】:Way to extend context of function? for example in decorator 【发布时间】:2021-10-28 14:28:33 【问题描述】:例如,如果想让装饰函数从闭包中查看记录器
def logged(func):
#here i want create logger which i want to be available from decorated function.
@wraps(func)
def _logged(*args, **kwargs):
return func(*args,**kwargs)
#naive idea hot to do it - obviously doesn't work
#return exec('func(*args,**kwargs)',dict(func=func,logger=logger,args=args,kwargs=kwargs),dict(func=func,logger=logger,args=args,kwargs=kwargs))
return _logged
【问题讨论】:
将记录器作为参数传递给修饰函数。请注意,这要求修饰函数接受 logger 参数(但调用者不需要传递它)。 【参考方案1】:这是(另一种)方法,但同样不是通过在编译时确定的闭包,因此不能在运行时添加。这个版本在函数的全局命名空间中添加了一个全局变量,注意保存和恢复已经存在的任何类似命名的变量的值。我从@Martijn Pieters'answer 得到了一些相关的问题:How to inject variable into scope with a decorator?
from functools import wraps
class Logger:
def __call__(self, *args, **kwargs):
return print(*args, **kwargs)
def logged(func):
logger = Logger() # Get or create value to be added.
varname = 'logger'
sentinel = object()
@wraps(func)
def _logged(*args, **kwargs):
namespace = func.__globals__
oldvalue = namespace.get(varname, sentinel) # Save prev value.
namespace[varname] = logger
try:
return func(*args, **kwargs)
finally:
if oldvalue is sentinel:
del namespace[varname]
else:
namespace[varname] = oldvalue
return _logged
@logged
def myfunc():
logger('Logged from myfunc.')
myfunc() # -> Logged from myfunc.
【讨论】:
你是最伟大的)非常感谢,这比我要求的还要多。 很高兴听到 ;¬) 不客气。但是@Martijn Pieters 设计这种方法值得称赞。感谢您为我提供了发现它的动力。【参考方案2】:通过闭包使其可见的另一种方法是使其成为装饰函数的属性。可运行示例:
from functools import wraps
class Logger:
def __call__(self, *args, **kwargs):
return print(*args, **kwargs)
def logged(func):
@wraps(func)
def _logged(*args, **kwargs):
return func(*args,**kwargs)
_logged.logger = Logger() # Make attribute of wrapped function.
return _logged
@logged
def myfunc():
myfunc.logger('Logged from myfunc')
myfunc() # -> Logged from myfunc
【讨论】:
【参考方案3】:只需将它作为装饰函数的前提条件,即您的函数将接收记录器作为参数:这不是 result 将采用的东西。 (这类似于 mock.patch
作为装饰器的工作方式。)
def logged(func):
logger = ...
@wraps(func)
def _logged(*args, **kwargs):
return func(*args, logger=logger, **kwargs):
return _logged
@logged
def foo(x, y, *, logger):
...
foo(3, 5)
尽管看起来,logger
不是一个必需的仅关键字参数,因为名称 foo
不再引用您的 3 参数函数。它指的是logged
创建的函数,当它最终被调用时,它将负责将记录器传递给您的原始 foo
。
如果你不喜欢 logger
看起来是必需的,你可以给它任何你喜欢的默认值,因为这个默认值永远不会被使用。
@logged
def foo(x, y, *, logger=None):
...
请注意,虽然_logged
是一个闭包,但func
是不是,因为闭包处理词法作用域,而func
未在定义logger
的词法作用域中定义.
【讨论】:
以上是关于扩展功能上下文的方法?例如在装饰器中的主要内容,如果未能解决你的问题,请参考以下文章