类
综述
Custom Classes
用户定义类:通过类定义创建
每个类通过字典对象
__dict__
实现独立的命名空间- 类属性引用被转化为在此字典中查找
- 其中未发现属性名时,继续在基类中查找
- 基类查找使用C3方法解析顺序,即MRO列表
- 也存在一些钩子对象允许其他定位属性的方式
当类属性引用yield类方法对象时,其将转化为
__self__
属性为当前类对象的实例方法对象当类属性引用yield静态方法对象时,其将转换为静态方法 对象所封装的对象
类属性复制会更新类字典,不会更新基类字典
类对象可被调用产生类实例
特殊属性
__bases__
:包含基类的元组,依在基类列表中出现的顺序
Class Instances
类实例:通过调用类对象创建
每个类实例都有一个通过字典对象
__dict__
实现的独立命名 空间- 属性引用首先在此字典中查找
其中未发现属性名时,继续在对应类属性中查找
- 用户定义函数对象:其会被转化为实例方法对象
__self__
属性即为该实例
- 静态方法、类方法对象:同样会被转化
- 描述器属性有特殊处理,实际存放在类
__dict__
中 对象不同
- 用户定义函数对象:其会被转化为实例方法对象
- 若未找到类属性,对象对应类具有
__getattr__()
方法, 将调用该方法
属性赋值、删除会更新实例字典,不会更新对应类字典
- 若类具有
__setattr__
、__delattr__
方法,将调用方法 而不是直接更更新对应实例字典
- 若类具有
特殊属性
__class__
:实例对应类
Classes
类:类对象通常作为“工厂”创建自身实例
__doc__
:类的文档字符串- 类定义第一条语句、且须为字符串字面值
- 没有则为
None
- 不会被子类继承
Class Instances
类实例:在所属类中定义__call__()
方法即成为可调用对象
属性
属性访问.
A.attr
被解释为type(A)中__getattribute__(A, attr)
.
的行为由python解释器定义type(A)中__getattribute__
的中用于强调不会再从type(type(A))
继续获取调用__getattibute__
则定义在类命名空间中函数是为实例定义
- 要为类定义方法应该自定义元类
测试代码
1
2
3
4
5
6
7
8
9class Meta(type):
def __getattribute__(self, attr):
print("--Meta--", attr)
return super().attr
class D(metaclass=Meta):
def __getattribute__(self, attr):
print("--Class--", attr)
return super().attr
__getattribute__
函数说明参见 cs_python/py3ref/special_methods
属性访问控制
- python没有没有属性访问控制,不依赖语言特性封装数据,而是 遵循一定属性、方法命名规约达到效果
__
开头属性:属性名称会被修改- 防止被派生类继承,此类属性无法通过继承覆盖
- 即若清楚代码会涉及子类,且应该在子类中隐藏起来,考虑 使用双下划线开头
- 通常是在属性名称前添加类名标记
_cls
- 但同时以
__
结尾属性名称不会被修改
单
_
开头属性:应被视为私有属性,不应被外部访问- python无法真正防止访问内部名称,但是这样会导致脆弱的 代码
此约定同样适用于模块名、模块级别函数
- 默认情况下,通配符
*
不会导入模块私有属性,除非 在配置有__all__
属性
- 导入参见cs_python/py3ref/simple_stmt#todo
- 默认情况下,通配符
单
_
结尾:避免定义的变量和保留关键字冲突
特殊属性
__dict__
:命名空间包含的属性__doc__
:文档字符串- 第一条语句、且须为字符串字面值
- 没有则为
None
- 不会被子类继承
__name__
:名称__qualname__
:qualified name,完整限定名称- 以点号分隔的名称
- 显示模块全局作用域到模块中某个定义类、函数、方法的 路径
__module__
:所属模块名称- 没有则为
None
- 没有则为
描述器属性
- 描述器协议参见cs_python/py3ref/special_methods
- 实例/类/静态方法:参见cs_python/py3ref/dm_gfuncs
@property
@property
装饰器:为类的属性增加处理逻辑,如:类型检查、
合法性验证
property属性和普通属性实现迥异,但使用类似
- property属性就是绑定有这些处理逻辑函数的类实例
- 访问、赋值、解除绑定时会自动触发
getter
、setter
、deleter
处理逻辑
property属性(或者说有效描述器)为类属性
- 一般需要通过在实例、或描述器命名空间
instance.__dict__
中存储数据,以实现对实例操作逻辑 独立 - 也可以实时计算属性值,此时无需为实例分别存储数据
- 初始化时,不应该直接设置底层数据属性,会绕过
setter
的参数检查
- 一般需要通过在实例、或描述器命名空间
- 过度使用
@property
时会降低代码可读性、效率,使用 get/set方法可能有更好的兼容性
代码实现
- 代码是C实现,这里是python模拟,和
help
结果不同
1 | class Property(object): |
@property
是描述器类,接受方法返回同名资料描述器
创建property属性
@property[.getter]
装饰getter-like方法得到同名资料 描述器返回描述器包含
.setter()
、.deleter()
方法/装饰器进一步 完善描述器@method.setter
:为描述器完善赋值处理逻辑@method.deleter
:为描述器完善del
处理逻辑
可以直接使用已有类中函数创建
property
类实例,得到 property属性(描述器)派生类中property属性覆盖
派生类中直接使用
@property
创建同名属性会覆盖基类 中property属性- 只有显式声明的处理逻辑被设置
- 基类中逻辑位于基类相应同名property属性,不会 被“隐式继承”
@<basecls>.<method>.getter/setter/deleter
单独覆盖 property属性方法- 但是
basecls
是硬编码方式,必须知道定义 property属性的具体类(或其子类)
- 但是
- 描述器协议、实现参见cs_python/py3ref/special_methods
示例
1 | class Student(object): |
1 | class Person: |
类继承
- 类继承会获得基类的所有方法
- 类里面的方法其实真的不是给类使用的,而是给实例使用
- 类自身使用的方法是元类中的方法
Method Resolution Order
MRO/方法解析顺序列表:包含当前类所有超类的线性顺序表
MRO列表顺序通过C3线性化算法实现,对每个类按以下规则合并 所有父类的MRO列表
- 子类先于父类检查
- 多个父类根据其在列表中的顺序被检查
- 若对下一个类存在多个合法的选择,选择第一个父类
为了实现继承,python会在MRO列表上从左到右开始查找超类, 直到第一个匹配这个属性的类为止
- 可以通过类
__mro__
、mro()
访问
super
1 | class super: |
参数
- 第一个参数:在MRO列表中定位类搜索起点(不包括)
- 第二个参数:提供MRO列表
- 类:直接传递MRO列表
- 实例:传递所属类的MRO列表
返回:封装有两个参数的
super
实例- 类似于返回MRO列表中某个类的实例,取决于访问的属性
用途:依次遍历MRO列表(指定位置开始)中类,查找指定属性
- 可以使用指定超类创建
super
实例,跳过对部分类搜索 - 只有MRO列表中每个类中的方法都
super()
调用,才能保证 列表中所有类的该方法都被链式调用
- 可以使用指定超类创建
说明
需要注意
super(cls, inst).__getattribute__("meth")
中 共有两段属性访问,两次访问调用不同__getattribute__
super(cls, inst).__getattribute__
首先调用super.__getattribute__
在type(inst).mro()
中寻找some_cls.__getattribute__
然后调用
some_cls.__getattrbibute__("meth")
访问meth
属性
应使用
super
访问基类属性,而不是直接使用基类名称,避免 多继承中出现问题- 继承链
super
保证方法只按找MRO列表顺序调用一次 - 多继承中硬编码基类名称调用方法可能导致方法被调用多次
- 继承链
super
访问的属性路线不够明确,所以需要遵循以下原则- 继承体系中,所有相同名字的方法拥有可兼容的参数名, 比如:相同参数个数、名称
- 最好确保最顶层类提供这个方法的实现,这样保证MRO上的 查找链肯定可以找到该方法
抽象类
接口、抽象类
抽象类无法直接实例化
- 目的就是让别的类继承它并实现特定的抽象方法
- 也可以通过注册方式让某类实现抽象基类
用途
- 通过执行类型检查,确保实现为特定类型、实现特定接口
- 类型检查很方便,但是不应该过度使用,因为动态语言目的就是 灵活性,强制类型检查让代码更复杂
- 使用
abc
模块方便定义抽象类
Mixins
Mixins:把一些有用的方法包装成Mixin类,用于扩展其他类的 功能,而这些类往往又没有继承关系
- Mixin不能直接实例化使用
- Mixin没有自己的状态信息,即没有定义
__init__
方法, 没有实例属性,因此Mixin中往往会定义__slots__ = ()
- Mixins讨论参见cs_program/program_design/inheritation
例
1 | class LoggedMappingMixin: |