装饰装饰器:尝试让我的头脑理解它

发布于 2024-11-06 09:16:09 字数 938 浏览 0 评论 0原文

我试图了解如何装饰装饰器,并想尝试以下操作:

假设我有两个装饰器并将它们应用于函数 hello()

def wrap(f):
    def wrapper():
        return " ".join(f())
    return wrapper


def upper(f):
    def uppercase(*args, **kargs):
        a,b = f(*args, **kargs)
        return a.upper(), b.upper()
    return uppercase

@wrap
@upper
def hello():
    return "hello","world"

print(hello())

然后我必须开始添加其他装饰器对于其他函数,但通常 @wrap 装饰器将“包装所有这些”

def lower(f):
    def lowercase(*args, **kargs):
        a,b = f(*args, **kargs)
        return a.lower(), b.lower()
    return lowercase

@wrap
@lower
def byebye():
    return "bye", "bye"

我如何编写一个装饰器,它装饰我的 @lower@upper装饰器?参见下文:

@wrap
def lower():
    ...

@wrap
def upper():
    ...

只需执行以下操作即可获得与上述相同的结果:

@upper
def hello():
    ...

@lower
def byebye():
    ...

I'm trying to understand how to decorate decorators, and wanted to try out the following:

Let's say I have two decorators and apply them to the function hello():

def wrap(f):
    def wrapper():
        return " ".join(f())
    return wrapper


def upper(f):
    def uppercase(*args, **kargs):
        a,b = f(*args, **kargs)
        return a.upper(), b.upper()
    return uppercase

@wrap
@upper
def hello():
    return "hello","world"

print(hello())

Then I have to start adding other decorators for other functions, but in general the @wrap decorator will "wrap all of them"

def lower(f):
    def lowercase(*args, **kargs):
        a,b = f(*args, **kargs)
        return a.lower(), b.lower()
    return lowercase

@wrap
@lower
def byebye():
    return "bye", "bye"

How do I write a decorator, which decorates my @lower and @upper decorators? See below:

@wrap
def lower():
    ...

@wrap
def upper():
    ...

To achieve the same result as above by only doing:

@upper
def hello():
    ...

@lower
def byebye():
    ...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

很酷又爱笑 2024-11-13 09:16:09
def upper(f):
    @wrap
    def uppercase(*args, **kargs):
        a,b = f(*args, **kargs)
        return a.upper(), b.upper()
    return uppercase

Python 中的装饰器

 @foo
 def bar(...): ...

相当于

 def bar(...): ...
 bar = foo(bar)

您想要获得的效果

@wrap
@upper
def hello(): ....

hello = wrap(upper(hello))

因此应该在 upper返回值上调用 wrap

def upper_with_wrap(f):
   def uppercase(...): ...
   return wrap(uppercase)

这也相当于在该函数上应用装饰器:

def upper_with_wrap(f):
   @wrap
   def uppercase(...): ...
   # ^ equivalent to 'uppercase = wrap(uppercase)'
   return uppercase
def upper(f):
    @wrap
    def uppercase(*args, **kargs):
        a,b = f(*args, **kargs)
        return a.upper(), b.upper()
    return uppercase

A decorator in Python

 @foo
 def bar(...): ...

is just equivalent to

 def bar(...): ...
 bar = foo(bar)

You want to get the effect of

@wrap
@upper
def hello(): ....

i.e.

hello = wrap(upper(hello))

so the wrap should be called on the return value of upper:

def upper_with_wrap(f):
   def uppercase(...): ...
   return wrap(uppercase)

which is also equivalent to applying the decorator on that function:

def upper_with_wrap(f):
   @wrap
   def uppercase(...): ...
   # ^ equivalent to 'uppercase = wrap(uppercase)'
   return uppercase
蓝戈者 2024-11-13 09:16:09

这是一个用装饰器装饰装饰器的通用(有点复杂)的解决方案(耶!)。

# A second-order decorator
def decdec(inner_dec):
    def ddmain(outer_dec):
        def decwrapper(f):
            wrapped = inner_dec(outer_dec(f))
            def fwrapper(*args, **kwargs):
               return wrapped(*args, **kwargs)
            return fwrapper
        return decwrapper
    return ddmain

def wrap(f):
    def wrapper():
        return " ".join(f())
    return wrapper


# Decorate upper (a decorator) with wrap (another decorator)
@decdec(wrap)
def upper(f):
    def uppercase(*args, **kargs):
        a,b = f(*args, **kargs)
        return a.upper(), b.upper()
    return uppercase

@upper
def hello():
    return "hello","world"

print(hello())

Here's a generic (and slightly convoluted) solution for decorating decorators with decorators (Yay!).

# A second-order decorator
def decdec(inner_dec):
    def ddmain(outer_dec):
        def decwrapper(f):
            wrapped = inner_dec(outer_dec(f))
            def fwrapper(*args, **kwargs):
               return wrapped(*args, **kwargs)
            return fwrapper
        return decwrapper
    return ddmain

def wrap(f):
    def wrapper():
        return " ".join(f())
    return wrapper


# Decorate upper (a decorator) with wrap (another decorator)
@decdec(wrap)
def upper(f):
    def uppercase(*args, **kargs):
        a,b = f(*args, **kargs)
        return a.upper(), b.upper()
    return uppercase

@upper
def hello():
    return "hello","world"

print(hello())
玩心态 2024-11-13 09:16:09

Boaz Yaniv 接受的答案可以稍微简化一下,如下所示:

def wrap(decorator):                # lowercase/uppercase decorator as argument
    def wrapperA(fn):               # decorated (hello) function as argument
        def wrapperB(*fn_args):     # decorated (hello) functions' arguments as arguments
            # Here, you tell your 'wrap' decorator to get the
            # result that we want to process/wrap further this way:
            t = decorator(fn)(*fn_args)
            return " ".join(t)
        return wrapperB
    return wrapperA


@wrap
def lower(f):
    def lowercase(*args, **kwargs):
        a, b = f(*args, **kwargs)
        return a.lower(), b.lower(), 'in lowercase'
    return lowercase


@wrap
def upper(f):
    def uppercase(*args, **kwargs):
        a, b = f(*args, **kwargs)
        return a.upper(), b.upper(), 'in uppercase'
    return uppercase


@lower
def hello_in_lc():
    return "HELLO", "WORLD"


@upper
def hello_in_uc():
    return "hello", "world"


x = hello_in_lc()
y = hello_in_uc()
print(x)
print(y)

输出:

hello world in lowercase
HELLO WORLD in uppercase

现在,wrap 装饰器具有如此多嵌套的原因是因为一旦您装饰了另一个装饰器的定义,整个执行过程变得与垂直嵌套装饰有点不同。这就是我要说的:

@wrap        # this wrap decorator is the one provided in the question
@upper
def hello()
    return "Hello", "World"

这就是 Python 对上述代码所做的事情。它以 upper 函数作为参数调用 wrap 装饰器。在 wrap 装饰器的 wrapper 内部,它发现装饰器正在被调用。因此它调用了 upper 函数。由于 upper 本身是函数的装饰器,因此在调用期间 upper 接收 hello 函数作为引用,其参数由包装器 接收>大写

uppercase 内部调用了 hello 函数,因此随后调用 hello 函数,处理其结果,并将其返回给 hello 函数wrap 装饰器中的 >wrapper 函数最终返回到主模块的全局作用域,其中 hello() 在底部被调用。

然而,装饰装饰器的定义是一个不同的故事。采取以下代码:

@wrap        # that's the wrap decorator from my answer
def upper()

@upper
def hello()

这里将发生的事情是,一旦您调用 hello()wrap 装饰器就会像前面的情况一样被调用,带有 upper > 函数作为参数传递给wrap。但是,如果您尝试在 wrapper 中调用 decorator 作为 decorator() Python 会抛出错误,指出 upper 函数是没有争论就打电话!

要修复 wrap 装饰器中的包装器(此处称为 wrapperA)需要接收参数。这个参数是对 upper 已修饰的函数的引用,在我们的例子中是 hello 函数。因此,wrapperA 必须使用 hello 函数 (fn) 作为参数来调用 装饰器

但是执行decorator(fn)将为我们提供对upper装饰器的uppercase包装器的引用。为了执行它并传递 fn 需要的任何参数,我们需要另一个名为 wrapperB 的包装器,嵌套在 wrapperA 中。在这里,wrapper A 实际上充当 fn 的装饰器,其 wrapperB 将采用 fn 的参数。

因此,用于获取对中间装饰器(下部和上部)结果进行任何处理的结果的复合最终调用应该如下所示:

def wrapperB(*fn_args):
    decorator(fn)(*fn_args)

# Alternative
    wrapped_fn = decorator(fn)
    result = wrapped_fn(*fn_args) 

这就是为什么必须嵌套的原因。

垂直装饰或 kennytm 此处 建议的替代方案显然对眼睛要好得多。

The accepted answer from Boaz Yaniv can be simplified a little bit like this:

def wrap(decorator):                # lowercase/uppercase decorator as argument
    def wrapperA(fn):               # decorated (hello) function as argument
        def wrapperB(*fn_args):     # decorated (hello) functions' arguments as arguments
            # Here, you tell your 'wrap' decorator to get the
            # result that we want to process/wrap further this way:
            t = decorator(fn)(*fn_args)
            return " ".join(t)
        return wrapperB
    return wrapperA


@wrap
def lower(f):
    def lowercase(*args, **kwargs):
        a, b = f(*args, **kwargs)
        return a.lower(), b.lower(), 'in lowercase'
    return lowercase


@wrap
def upper(f):
    def uppercase(*args, **kwargs):
        a, b = f(*args, **kwargs)
        return a.upper(), b.upper(), 'in uppercase'
    return uppercase


@lower
def hello_in_lc():
    return "HELLO", "WORLD"


@upper
def hello_in_uc():
    return "hello", "world"


x = hello_in_lc()
y = hello_in_uc()
print(x)
print(y)

Output:

hello world in lowercase
HELLO WORLD in uppercase

Now, the reason the wrap decorator has so much nesting is because once you decorate another decorator's definition, the whole execution process becomes a bit different than the vertical nested decoration. Here's what I am talking about:

@wrap        # this wrap decorator is the one provided in the question
@upper
def hello()
    return "Hello", "World"

Here's what Python does with the above code. It calls the wrap decorator with upper function as argument. Inside wrapper of wrap decorator it finds that the decorator is being invoked. So it invokes it, the upper function. Because upper itself is a decorator of a function, during its invocation upper receives hello function as a reference and its arguments are received by wrapper uppercase.

Inside uppercase a call to hello function is made, so hello function is then invoked, its result is processed, and is returned back to wrapper function in wrap decorator which is finally returned back to main module's global scope where hello() was invoked at the bottom.

However, decorating a decorator's definition is a different story. Take the following code:

@wrap        # that's the wrap decorator from my answer
def upper()

@upper
def hello()

What's gonna happen here is once you invoke hello() the wrap decorator would be invoked like in the earlier situation, with upper function passed as an argument to wrap. However, if you try to invoke decorator inside wrapper as decorator() Python would throw error that upper function was called without an argument!

To fix that your wrapper (called wrapperA here) in wrap decorator needs to receive an argument. This argument is the reference to the function that upper has decorated, which is hello function in our case. So the wrapperA has to call the decorator with hello function (fn) as argument.

But executing decorator(fn) would give us the reference to upper decorator's uppercase wrapper. In order to execute it and to pass any argument that fn would need we need another wrapper called wrapperB nested in wrapperA. Here, wrapper A would literally act as a decorator of fn and its wrapperB would take fn's arguments.

So the compounded final call to get the result for any processing on intermediate decorators' (lower and upper) result should look like this:

def wrapperB(*fn_args):
    decorator(fn)(*fn_args)

# Alternative
    wrapped_fn = decorator(fn)
    result = wrapped_fn(*fn_args) 

And that's why so must nesting is needed.

Vertical decorating or the alternative suggested by kennytm here is obviously much better on the eyes.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文