Post

《Effective Python》笔记-第6章 元类与属性

第四十四条:用纯属性与修饰器取代旧式的setter与getter方法

给新类定义接口时,应该先从简单的public属性写起,避免定义setter与getter方法。

如果在访问属性时确实有必要做特殊处理,那就通过@property来定义获取属性与设置属性的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
class VoltageResistance(Resistor):
    def __init__(self, ohms):
        super().__init__(ohms)
        self._voltage = 0
    
    @property
    def voltage(self):
        return self._voltage
    
    @voltage.setter
    def voltage(self, voltage):
        self._voltage = voltage
        self.current = self._voltage / self.ohms

实现@property方法时,应该遵循最小惊讶原则,不要引发奇怪的副作用。

@property方法必须执行得很快。复杂或缓慢的任务,尤其是设计I/O或者会引发副作用得那些任务,还是用普通的方法来实现比较哈。

第四十五条:考虑用@property实现新的属性访问逻辑,不要急着重构原有的代码

Python内置的@property修饰器使开发者很容易就能实现出灵活的逻辑,使得程序在获取或设置相关属性时,触发这些逻辑。

可以利用@property给已有的实例属性增加新的功能。

可以利用@property逐渐改善数据模型而不影响已经写好的代码。

如果发现@property使用太过频繁,那可能就该考虑重构这个类了,同时按照旧办法使用这个类得那些代码可能也要重构。

第四十六条:用描述符来改写需要复用的@property方法

Python内置的@property机制最大的缺点就是不方便复用。如果想复用@property方法所实现的行为与验证逻辑,则可以考虑自己定义描述符类。

为了防止内存泄漏,可以在描述符中用WeakKeyDictionary取代普通的字典。不要太纠结于__getattrbute__是怎么通过描述符协议来获取并设置属性的。

第四十七条:针对惰性属性使用__getattr__、getattribute__及__setattr

如果想用自己的方式(例如惰性地或者按需地)加载并保存对象属性,那么可以在该对象所属的类里实现__getattr__与__setattr特殊方法。

__getattr只会在属性确实时触发,而__getattribute__则在每次访问属性时都要触发。

在实现__getattribute__与__setattr__的过程中,如果要使用本对象的普通属性,那么应该通过super()来使用,而不要直接使用,以避免无限递归。

第四十八条:用__init_subclass__验证子类写的是否正确

如果某个类是根据元类所定义的,那么当系统把该类的class语句体全部处理完之后,就会将这个类的写法告诉元类的__new__方法。

可以利用元类在类创建完成前检视或修改开发者根据这个元类所定义的其他类,但这种机制通常显得有点笨重。

__init_subclass__能够用来检查子类定义得是否合理,如果不合理,那么可以提前报错,让程序无法创建出这种子类的对象。

在分层得或者设计多重继承的类体系里面,一定别忘了在你写的这些类的__init_subclass__内通过super()来调用超类的__init_subclass__方法,以便按正确的顺序触发各类的验证逻辑。

第四十九条:用__init_subclass__记录现有的子类

类注册(Class registration)是个相当有用的模式,可以用来构建模式块的Python程序。

我们可以通过基类的元类把用户从这个基类派生出来的子类自动注册给系统。

利用元类实现类注册可以防止由于用户忘记注册而导致程序出现问题。

优先考虑通过__init_subclass__实现自动注册,而不要用标注的元类机制来实现,因为__init_subclass更清晰,更便于初学者理解。

第五十条:用__set_name__给类属性加注解

元类可以当作class语句的挂钩,只要class语句体定义完毕,元类就会看到它的写法并尽快做出应对。

如果某个类用__set_name__描述符的实例来定义字段,那么系统就会在描述符上面出发这个特殊方法。

我们可以通过元类吧利用这个元类所定义的其他类拦截下来,从而在程序开始使用那些类之前,先对其中定义的属性做出修改。

描述符与元类搭配起来,可以形成一套强大的机制,让我们既能采用声明式的写法来定义行为,又能在程序运行时检视这个行为的具体执行情况。

用描述符直接操作每个实例的属性字典,要比把所有实例的属性都放到一份字典里更好,因为后者要求我们必须使用weakref内置模块之中的特殊字典来记录每个实例的属性值以防止内存泄漏。

第五十一条:优先考虑通过类修饰器来提供可组合的扩充功能,不要使用元类

尽管元类允许我们用各种方式来定制其他类的创建逻辑,但有些情况它未必能够处理得好。可以使用类修饰器。

类修饰器其实就是个函数,只不过它可以通过参数获知自己所修饰的类,从而重建或调整这个类并返回修改结果。

如果要给类中的每个方法或属性都施加一套逻辑,而且还想尽量少写一些例行代码,那么类修饰器是个很值得考虑的方案。

元类之间很难组合,而类修饰器比较灵活,可以施加在同一个类上,并且不会发生冲突。

第7章 并发与并行 暂不阅读。

This post is licensed under CC BY 4.0 by the author.