V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
saximi
V2EX  ›  Python

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

  •  
  •   saximi · 2017-08-22 21:32:39 +08:00 · 1768 次点击
    这是一个创建于 2654 天前的主题,其中的信息可能已经有所发展或是发生改变。

    在看《 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

    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('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 应该起到的是同样的效果, 为何第二个装饰器中没有发生新实例覆盖旧实例的现象呢?

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

    1 条回复    2017-08-22 21:51:26 +08:00
    chenstack
        1
    chenstack  
       2017-08-22 21:51:26 +08:00
    1. 这是不带参数的类装饰器,Person 对应 aClass 这个形参。之后使用的 Person 实际是 Tracer 的一个实例。而两次 Person(),其实是在这个实例上调用__call__。所以只有一次__init__,wrapped 会覆盖。
    2. Private 装饰器返回的是函数内声明 onInstance 类,每次 Doubler()会新创建实例,所以有多个__init__调用,故没有覆盖。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5726 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 03:35 · PVG 11:35 · LAX 19:35 · JFK 22:35
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.