装饰器工厂返回 lambda

Decorator factory returning lambda

我遇到了以下代码(这是对真实代码的简化),它结合了装饰器工厂和 lambda 用法:

from functools import wraps

def decorator_factory(arg1):
    def decorator(arg2):
        return lambda func: real_decorator(arg1, arg2, func)
    return decorator

def real_decorator(prefix: str, perm: str, func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result
    return wrapper

my_decorator_instance = decorator_factory("r")

class MyClass():

    def __init__(self, *args, **kwargs):
        pass

    @my_decorator_instance('decorator args')
    def method_1(self):
        print("method_1")


m = MyClass()
m.method_1()

该代码运行良好,但我不太了解其机制。特别是作为装饰器返回时如何使用 lambda 值。请注意 decorator_factory 中没有 no func 参数。 func 是 lambda 的参数。

它只是返回一个匿名函数,而不是使用 def 语句。以下是完全等价的:

def decorator_factory(arg1):
    def decorator(arg2):
        def _(func):
            return real_decorator(arg1, arg2, func)
        return _
    return decorator

decorator_factory("r") returns 绑定到名称 my_decorator_instance 的函数; my_decorator_instance('decorator_args') returns 应用于 method_1 的函数。


为了更好地了解装饰器的工作原理(它们只是应用于其他事物的函数),您也可以编写

class MyClass():

    def __init__(self, *args, **kwargs):
        pass

    @decorator_factory("r")('decorator args')
    def method_1(self):
        print("method_1")

甚至

class MyClass():

    def __init__(self, *args, **kwargs):
        pass

    method_1 = decorator_factory("r")('decorator args')(lambda self: print("method_1"))

为了更好地了解各种嵌套函数是如何生效的,您可以想象它们在每次函数调用时被“展开”(相应的函数参数被函数参数替换)。所以你的示例代码大致等同于以下内容:

def decorator_factory(arg1):
    def decorator(arg2):
        return lambda func: real_decorator(arg1, arg2, func)
    return decorator


# The following function call effectively "unwraps" the `decorator_factory` function:
# `my_decorator_instance = decorator_factory("r")`
# is equivalent to (`arg1` being replaced by "r"):
def my_decorator_instance(arg2):
    return lambda func: real_decorator("r", arg2, func)


# Now unwrapping `my_decorator_instance`:
# `@my_decorator_instance("decorator args")`
# is equivalent to (`arg2` being replaced by "decorator args"):
@(lambda func: real_decorator("r", "decorator args", func))

对于最后一步,即应用装饰器的地方,lambda 函数捕获 already-specified 参数("r""decorator args")并且它还需要注意将 func 参数放在函数调用 real_decorator.

的正确位置(第三个)

通过将 functools.partialreal_decorator 结合使用可以实现类似的效果(即 partial 接替 decorator_factory 的工作):

from functools import partial

[...]

my_decorator_instance = partial(real_decorator, "r")

class MyClass:
    @partial(my_decorator_instance, "decorator args")
    def method_1(self):
        pass

造成混淆的一个可能原因是 decorator_factory 函数不是 装饰器工厂 ,而是 (装饰器工厂) 工厂.

一个装饰器工厂是一个returns一个装饰器的函数(见). For example, functools.lru_cache是一个装饰器工厂因为它 returns 一个装饰器:

import functools

decorator = functools.lru_cache(maxsize=69)

@decorator
def compute_something_expensive(foo, bar):
    ...

但是你的函数returns是一个装饰器工厂,而不是装饰器。您可以像这样重构它:

def decorator_factory_factory(arg1):
    def decorator_factory(arg2):
        def decorator(func):
            return real_decorator(arg1, arg2, func)
        return decorator
    return decorator_factory

你不能像这样使用函数,因为它不是装饰器工厂:

# will not really work:
@decorator_factory_factory('foo')
def method_1(self):
    print("method_1")

你必须这样使用它:

@decorator_factory_factory('foo')('bar')
def method_1(self):
    print("method_1")

# alternatively:
decorator_factory = decorator_factory_factory('foo')

@decorator_factory('bar')
def method_1(self):
    print("method_1")

让我们关注这部分代码:

my_decorator_instance = decorator_factory("r")

class MyClass():

    def __init__(self, *args, **kwargs):
        pass

    @my_decorator_instance('decorator args')
    def method_1(self):
        print("method_1")


m = MyClass()
m.method_1()

第一行(函数调用)将在 arg1 上应用“r”,同样,装饰器正用于值为“decorator args”的方法,这意味着该值将应用于 arg2 ,然后该方法将应用于 func 参数

请记住,您可以在 python 中的函数调用中使用链接,因此上面的代码等同于:

decorator_factory("r")("decorator args")(method_1)

现在对于装饰器中的 lambda 部分,为了自己简化它,尝试将 lambda 转换为函数,它变成:

def function(func):
    return real_decorator(arg1,arg2,func)

所以它没有什么神奇之处。