《Python 学习手册》中关于装饰器的疑问

在看《 PYTHON 学习手册》这本书关于装饰器的内容时,遇到了一个费解的问题,涉及两个装饰器。

一、第一个装饰器

class Tracer:

def __init__(self, aClass): 
print("init")

self.aClass = aClass 

def call(self, *args):

self.wrapped = self.aClass(*args)  #语句 1

return self

def getattr(self, attrname):

print('Trace: ' + attrname)

return getattr(self.wrapped, attrname)

[@Tracer](/user/Tracer)

class Person:

def __init__(self, name):  
self.name = name

bob = Person('Bob')

print(bob.name)

sue = Person('Sue')

print(sue.name)

print(bob.name) #bob.namesue.name 覆盖

上面代码输出如下:

init

Trace: name

Bob

Trace: name

Sue

Trace: name

Sue

可见 bob.name 被后创建的 sue.name 覆盖了。书上对此现象的解释是:该装饰器对于一个给定的类的多个实例并不是很有效,每个实例构建调用会触发__call__,这会覆盖前面的实例。直接效果是 Tracer 只保存了一个实例,即最后创建的一个实例。但是这个说法对于下面的第二个装饰器好像就不适用了。

二、第二个装饰器

def Private(*privates):

def onDecorator(aClass):  
class onInstance: 

	def __init__(self, *args, **kargs):

		print("init")

		self.wrapped = aClass(*args, **kargs)    #语句 2

	def __getattr__(self, attr): 

		if attr in privates:

			raise TypeError('private attribute fetch: ' + attr)

		else:

			return getattr(self.wrapped, attr)

	def __setattr__(self, attr, value):

		if attr == 'wrapped':  

			self.__dict__[attr] = value  

		elif attr in privates:

			raise TypeError('private attribute change: ' + attr)

		else:

			setattr(self.wrapped, attr, value)  

return onInstance  

return onDecorator

if name == 'main':

[@Private](/user/Private)('data', 'size')  

class Doubler:

def __init__(self, label, start):

	self.label = label 

	self.data = start  

X = Doubler(‘X is’, [1, 2, 3])

Y = Doubler(‘Y is’, [-10, -20, -30])

print(“X.label1=”,X.label)

print(‘Y.label=’,Y.label)

print(“X.label2=”,X.label)

上面的代码输出如下:

init

init

X.label1= X is

Y.label= Y is

X.label2= X is

我的问题如下:

1、同样是将类实例化两次,为何第一个装饰器中__init__只执行一次,但是第二个装饰器却执行了两次__init__?

2、第二个装饰器中,虽然 Y 是在 X 后面才创建的,但是 X.label 并没有被 Y.label 覆盖! 我觉得第一个装饰器的语句 1 和第二个装饰器的语句 2 应该起到的是同样的效果, 为何第二个装饰器中没有发生新实例覆盖旧实例的现象呢?

恳请大家指点,万分感谢!


《Python 学习手册》中关于装饰器的疑问

2 回复

《Python学习手册》里讲装饰器那块确实容易绕晕,我来帮你理一下核心。

装饰器本质上就是个函数包装器。你写个函数,用@decorator往上一放,它就偷偷把原函数给“装饰”了。书里那套__call__和嵌套函数的解释太学术化,咱们看个实际例子:

def log_calls(func):
    def wrapper(*args, **kwargs):
        print(f"调用函数: {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@log_calls
def add(a, b):
    return a + b

print(add(2, 3))  # 先打印"调用函数: add",再返回5

这里log_calls就是个装饰器。它吃进一个函数func,吐出一个新函数wrapperwrapper干了两件事:先打印日志,再调用原来的func@log_calls这语法糖就等于add = log_calls(add)

装饰器还能带参数,那就得再套一层:

def repeat(n):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(n):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(3)
def greet(name):
    print(f"Hello {name}")

greet("World")  # 会打印三次Hello World

关键就三点:1)装饰器返回的是个函数;2)@语法只是语法糖;3)wrapper要用*args, **kwargs来保持原函数的参数灵活性。

书里那些__call__的例子是面向对象实现方式,和函数式实现本质一样。刚开始学就记住上面这个模式,够用了。

总结建议:把装饰器理解成函数的“包装纸”就行。


  1. 这是不带参数的类装饰器,Person 对应 aClass 这个形参。之后使用的 Person 实际是 Tracer 的一个实例。而两次 Person(),其实是在这个实例上调用__call__。所以只有一次__init__,wrapped 会覆盖。
    2. Private 装饰器返回的是函数内声明 onInstance 类,每次 Doubler()会新创建实例,所以有多个__init__调用,故没有覆盖。
回到顶部