python-3.x 如何在组合装饰器时向装饰函数添加属性?

f45qwnt8  于 12个月前  发布在  Python
关注(0)|答案(2)|浏览(141)

定义了两个函数装饰器。目标是检测一个函数是否应用了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。

gywdnpxw

gywdnpxw1#

实际上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函数对象。它们适用于“their” Package 函数。 Package 函数是这两个装饰器中的两个不同对象:

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


在堆叠装饰器之后,你可以期望decorator1.<locals>.wrappeddec1(因为它是外部的),但它没有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并将其添加到返回的 Package 函数中。我还将decX属性添加到原始的myfunc中。

aoyhnmkz

aoyhnmkz2#

与从每个装饰器添加单独的属性不同,您可以让装饰器协作并将它们的标记添加到共享属性(列表)中。

def decorator1(f):
    decorators = getattr(f, "dec", [])
    decorators.append("dec1")

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

    wrapped.dec = decorators
    return wrapped

def decorator2(f):
    decorators = getattr(f, "dec", [])
    decorators.append("dec2")

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

    wrapped.dec = decorators
    return wrapped

def f0():
    print("running f0")

@decorator1
def f1():
    print("running f1")

@decorator2
def f2():
    print("running f2")

@decorator1
@decorator2
def f3():
    print("running f3")

if __name__ == "__main__":
    f0()
    f1()
    f2()
    f3()
    print(f"f0 has decorators: {getattr(f0, 'dec', [])}")  # []
    print(f"f1 has decorators: {getattr(f1, 'dec', [])}")  # ['dec1']
    print(f"f2 has decorators: {getattr(f2, 'dec', [])}")  # ['dec2']
    print(f"f3 has decorators: {getattr(f3, 'dec', [])}")  # ['dec2', 'dec1']

字符串

相关问题