组合装饰器时如何给被装饰函数添加属性?

问题描述 投票:0回答:1

定义了两个函数装饰器。 目标是检测函数是否应用了 0、1 或 2 个装饰器。

为什么下面的代码为第二个装饰器返回“False”?

def decorator1(f):
    def wrapped(*args, **kwargs):
        f(*args, **kwargs)

    wrapped.dec1 = True
    return wrapped


def decorator2(f):
    def wrapped(*args, **kwargs):
        f(*args, **kwargs)

    wrapped.dec2 = True
    return wrapped


@decorator1
@decorator2
def myfunc():
    print(f"running myfunc")


if __name__ == "__main__":
    myfunc()

    print(f"myfunc has decorator1: {getattr(myfunc, 'dec1', False)}")
    print(f"myfunc has decorator2: {getattr(myfunc, 'dec2', False)}")

结果:

running myfunc
myfunc has decorator1: True 
myfunc has decorator2: False

我使用的是Python 3.9。

python python-3.x python-decorators
1个回答
0
投票

实际上 print 语句中的这句话是不正确的:“myfunc has...”。不。

myfunc
既没有
dec1
也没有
dec2
。调用装饰器返回的是一个新函数,而不是您传递的函数。我来解释一下。

此代码:

@decorator1
@decorator2
def myfunc():
    print(f"running myfunc")

相当于:

def myfunc():
    print('running myfunc')

myfunc = decorator2(myfunc)
myfunc = decorator1(myfunc)

请记住,您将这些动态属性添加到

wrapped
函数中。它们并不都适用于同一个
myfunc
函数对象。它们适用于“他们的”包装函数。包装的函数是这两个装饰器中的两个不同的对象:

myfunc = decorator2(myfunc)
print(myfunc)   # <function decorator2.<locals>.wrapped at 0x1028456c0>
myfunc = decorator1(myfunc)
print(myfunc)   # <function decorator1.<locals>.wrapped at 0x102845760>

堆叠装饰器后,您可以预期

decorator1.<locals>.wrapped
具有
dec1
(因为它是外部的),但它没有
dec2
。就在
decorator2.<locals>.wrapped
:

def decorator1(f):
    def wrapped(*args, **kwargs):
        return f(*args, **kwargs)
    wrapped.dec1 = True
    return wrapped


def decorator2(f):
    def wrapped(*args, **kwargs):
        return f(*args, **kwargs)
    wrapped.dec2 = True
    return wrapped


def myfunc():
    print('running myfunc')


myfunc = decorator2(myfunc)
myfunc = decorator1(myfunc)


print(f"{getattr(myfunc, 'dec1', False)}")  # True
print(f"{getattr(myfunc.__closure__[0].cell_contents, 'dec2', False)}")  # True

如果有这些装饰器,你的假设就会起作用:

def decorator1(f):
    f.dec1 = True
    return f

def decorator2(f):
    f.dec2 = True
    return f

@decorator1
@decorator2
def myfunc():
    print('running myfunc')

print(f"{getattr(myfunc, 'dec1', False)}")  # True
print(f"{getattr(myfunc, 'dec2', False)}")  # True

如何用现有代码实现它?

我认为这不是一个干净的方法,但如果你愿意的话它可以工作:

def decorator1(f):
    def wrapped(*args, **kwargs):
        return f(*args, **kwargs)

    while hasattr(f, '__wrapped__'):
        f = f.__wrapped__
    f.dec1 = True
    wrapped.__wrapped__ = f
    return wrapped

def decorator2(f):
    def wrapped(*args, **kwargs):
        return f(*args, **kwargs)

    while hasattr(f, '__wrapped__'):
        f = f.__wrapped__
    f.dec2 = True
    wrapped.__wrapped__ = f
    return wrapped

@decorator1
@decorator2
def myfunc():
    print('running myfunc')

print(f"{getattr(myfunc.__wrapped__, 'dec1', False)}")  # True
print(f"{getattr(myfunc.__wrapped__, 'dec2', False)}")  # True

你看,通过 while 循环,我深入内部找到原始的

myfunc
并将其添加到返回的包装函数中。我还将
decX
属性添加到原始
myfunc

© www.soinside.com 2019 - 2024. All rights reserved.