扩展功能上下文的方法?例如在装饰器中

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 的词法作用域中定义.

【讨论】:

以上是关于扩展功能上下文的方法?例如在装饰器中的主要内容,如果未能解决你的问题,请参考以下文章

是否可以在 Typescript 中装饰装饰器?

我应该如何处理@jwt_required 装饰器中引发的异常? (在烧瓶-jwt-扩展中)

装饰模式

JAVA设计模式之装饰者模式

JAVA基础——设计模式之装饰者模式

装饰设计