python魔术方法和元类介绍
站内链接:
初始化和构造
__new__
格式:__new__
(self, …)
功能:控制实例对象的创建,负责返回一个实例对象。除非你要继承不可变类型例如(tuple, str, unicode),否则一般不会 override 该方法
PS:一般情况下,使用 Factory 来代替使用__new__
方法
__init__
格式:__init__
(self, …)
功能:负责对象的初始化工作,此时实例对象已经创建,不返回任何值
__del__
格式:__del__
(self)
功能:析构器,但是并非 del x 的实现逻辑,用于定义一个对象进行 GC 操作时的行为,但是如果对象仍旧被引用则 GC 无法作用。
__copy__
格式:__copy__
(self)
功能:浅复制,copy.copy()时调用
__deepcopy__
格式:__deepcopy__
(self, memodict={})
功能:深复制,copy.deepcopy(object)时调用
__mro__
格式:__mro__
(cls)
功能:祖先链查找,另见mro legb.
运算符
逻辑运算
__cmp__
格式:__cmp__
(self, other)
功能:最基本的用于比较的魔术方法,但是可能不同的比较符号的规则可能不一样。
PS:python3 中被废弃,使用下面的 6 个函数(operator 模块中)
__eq__
格式:__eq__
(self, other)
功能:实现==的行为逻辑
等式比较级别:
- 相同的 hash 值,见下面的
__hash__
- 等号比较,hash 值相等 + 值相等, 见==操作符
- 相等的 IDD, hash 值相等 + 值相等 + 同一对象,见 is 操作符
3 . __ne__
格式:__ne__
(self, other)
功能:实现!=的行为逻辑
__lt__
格式:__lt__
(self, other)
功能:实现< 的行为逻辑
__gt__
格式:__gt__
(self, other)
功能:实现> 的行为逻辑
__le__
,__ge__
功能: 大于等于, 小于等于
算数运算
一元操作符
1 | # 1. 实现+正号的特性 |
算数操作符
1 | # 1. 加法 |
反运算
即将自己当做操作符的第二个操作对象,其他和上面的算法运算符是一直的,一般情况下,正运算和反运算结果一直。
1 | __radd__(self, other) # 反加 |
2.2..4 增量赋值
增量赋值语句,将操作符和赋值结合在一起。
1 | __iadd__(self, other) #+=赋值加法 |
位运算
1 | # 1. 左移, 5 << 1 == binary(101) << 1 == binary(1010) |
类型转换
通用
类型转换魔术方法,用以实现内置类型转换
1 | __int__(self) #整形 |
__index__
类实例作为切片的整型值,常常用于多进制代码中,例如:
1 | class A8: |
类输出或者表现类
字符串表示一个类,实现 human 可读性,记录日志时更为简单明了
__str__
格式:__str__
(self)
功能:调用 str(object)时的输出值,该值为 human 可读
__repr__
格式:__repr__
(self)
功能:调用 repr(object)时的输出值,该值为机器可读,通常情况下 obj == eval(repr(obj)),repr 实现的功能类似``。
其他说明:
1 | 默认情况下,如果__repr__定义了,但是__str__未定义, 则会执行__str__=__repr__; |
__unicode__
格式:__unicode__
(self)
功能:调用 unicode(object)时的输出值,返回 unicode 字符串
PS:在 python2 下面用以替换__str__
避免出错,python3 中直接使用__str__
,新增__bytes__
__hash__
参考:https://segmentfault.com/a/1190000003969462
格式:__hash__
(self)
功能:调用 hash(object)时的输出值–整形。对于 mutable,该函数返回 None。 当手动为一个对象设置一个 hash 值时(immutable),那么该 object 就可以作为字典的 key 存在,关于等值比较见上面的__eq__
说明.
值:默认情况下 hash 值 == id(object)/16
相等性检测
1 | # 1 不可变对象 |
冻结版本
1 | 可以使用冻结版本对mutable对象进行hash操作,类似版本控制,fronzenset就是如此。 |
__nonzero__
格式: __nonzero__
(self)
功能:调用 bool(object)时的返回值,必须返回 true 或者 false
PS:在 Python3 中更名为__bool__
__format__
格式:__format__
(self, formatstr)
功能:格式化展示对象,’{0:abc}’.format(a)等价于 format(a, ‘abc’), 等价于 a.__format__
(‘abc’),实现了对内建 format 方法的封装
例子:见 magic-method/test_format.py 中的 Foo 类,另见笔记<字符串输出>
__sizeof__
格式:__sizeof__
(self)
功能:使用 sys.getsizeof(object)时调用,返回对象的大小,单位为 bytes
属性控制
完成类的封装,私有属性以及相应的 getter、setter 方法。
可以用于实现计数功能,每一次赋值,counter+1
__getattr__
格式:__getattr__
(self, name)
功能:当试图获取一个不存在的属性时,发生的行为操作,当且仅当属性不存在的是该方法才会被调用
__setattr__
格式:__setattr__
(self, name, value)
功能:当试图赋值一个属性时(不管是否存在),发生的行为操作,避免递归操作
例子:
1 | def __setattr__(self, name, vlaue): |
__delattr__
格式:__delattr__
(self, name)
功能:删除一个属性时,发生的行为操作,del self.name,避免递归操作
__getattribute__
(存在默认值)
格式:__getattribute__
(self, name)
功能:当访问一个存在的属性时调用(先查看是否存在该属性,如果不存在,默认会调用__getattr__
, 如果没有定义__getattr__
, 则抛出异常 AttributeError 异常)
PS:__getattribute__
在很多地方有默认行为,例如下面的 descriptor,所以一般不建议 rewrite 该方法。
默认情况下, 可以对类或者对象添加新的属性值, 并可以获取属性的值
hasattr
格式:hasattr(obj, attr)
功能:检查 obj 是否存在 attr 属性
PS: 对__getattr__
的封装, 抛异常表示没有
getattr
格式:getattr(obj, attr)
功能:获取 Obj 中的 attr 值
setattr
格式:setattr(obj, attr, value)
功能:设置值
实例代码
见 python/src/magic-method/test_attribute.py
序列
将类定制成类似 list,tuple,string,dict 的内置序列功能,使用非常广泛
协议或者条件
python 中的协议更像一种编程指南,必须按照某些特定的格式才能实现序列功能。
- 不可变容器:
__len__
,__getitem__
- 可变容器:
__len__
,__getitem__
,__setitem__
,__delitem__
- 可迭代:
__iter__
, next, 见< Python 核心编程 202 页>说明
__len__
格式:__len__
(self)
功能:容器长度
__getitem__
格式:__getitem__
(self, key)
功能:可以使用 self[key]访问某一个条目
PS:注意和__getattribute__
以及__getattr__
区分开来, 前者使用 obj[key]来访问, 后者使用 obj.key 访问属性.
__setitem__
格式:__setitem__
(self, key, value)
功能:定义一个条目被赋值时的行为,self[key] = value,需要做好如何插入的代码
__delitem__
格式:__delitem__
(self, key)
功能:一个条目被删除时的行为,del self[key],如果从对象中删除一个元素
__iter__
格式:__iter__
(self)
功能:返回一个容器的迭代器,即返回对象本身,返回 self
why?
- 可扩展的迭代器定义
- 对列表迭代, 字典迭代带来了性能上的提升
- 创建真正的迭代接口, 并且向后兼容
- 迭代非序列集合时,能够提供更加简洁可读的代码
how?
- 我的下一条数据在哪里? 请见– next 函数的传奇历史
- 我怎么知道数据全部取完了? 请见– StopIteration 这个神奇的正义之法.
Using: enumerate(), reversed(), dict1.iteritems(), iter(object)
__reversed__
格式:__reversed__
(self)
功能:reversed()调用时发生的行为,返回列表的反转版本
__contains__
格式:__contains__
(self, item)
功能:调用 in, not in 来测试成员是否存在时需要做的操作,返回 true/false
__concat__
格式:__concat__
(self, other)
功能:连接两个序列时需要做的操作,在+操作符调用触发
反射
控制 isinstance()和 issubclass()内置方法的反射行为
__instancecheck__
格式:__instancecheck__
(self, instance)
功能:判断 instance 是否为 class 的一个实例
实现:isinstance(object, classinfo)时调用
__subclasscheck__
格式:__subclasscheck__
(self, subclass)
功能:检查一个类是不是定义的类的子类
实现:issubclass(subclass, class)时调用
__dir__
格式:__dir__
(object)
功能:返回对象的大部分属性字典,dir(object),一般不会 rewrite 该方法,并无太大意义
callable
让类的”实例”行为表现的像函数一样,从而实现将一个函数当成参数进行传递,实现各种功能
格式:__call__
(self, [args…])
功能:调用 x()时触发, 其中 x 为实例对象
会话管理
会话控制器:包装 with 语句来设置和清理行为,类似 setUp 和 tearDown 的行为,事务性行为
__enter__
格式:__enter__
(self)
功能:定义使用 with 语句时,应该初始块被创建时的行为,其中__enter__
的返回值被 with 语句的目标或者 as 后的名字绑定
__exit__
格式:__exit__
(self, exception_type, exception_value, traceback)
功能:定义一个代码块被执行时或者终止会话时,应该做的操作:处理异常、清除工作、收尾工作,如果需要直接抛出异常,则该函数返回 false。
参数:如果发生异常,通过三个参数中的栈信息返回
描述器(descriptor)
Introduction
参考:http://pyzh.readthedocs.io/en/latest/Descriptor-HOW-TO-Guide.html
内置的描述器有:property, staticmethod,instance method, classmethod, super。
descriptor 仅仅在新类中生效
Theory
一个描述器是一个有"绑定行为"的对象属性(attribute),通过`__get__`, `__set__`, `__del__`来控制某一个属性的get/set/delete操作。
默认查找
默认的属性 get/set/delete 操作,都是通过如下的查找链来对属性进行操作的,比如需要获取对象 x1 中的 name 值,其中类为 XClass:
- 从 x1.
__dict__
中获取 name 值,如果存在,直接返回 - 从 XClass.
__dict__
中获取 name 值,如果存在,直接返回 - 从 XClass 父类的
__dict__
中获取 name 值,直到碰到 metaclass,抛出异常
但是, 一旦变为描述器, 则会根据类型(资料描述器/数据描述器, 参考 javascript 的数据属性, 存取器属性含义)来进行不同的查找操作.
descriptor
在 13.3 的查找过程中,一旦碰到 descriptor,则 python 解释器会调用描述器的方法来重写这个操作,返回所需的属性值。
整个描述器调用都是因为__getattribute__
作为中转器:
- rewrite 会覆盖默认行为
__getattribute__
仅仅对新式类生效- object.
__getattribute__
和 type(object).__getattribute__
的调用不一样
对象调用过程:
1 | 发现x1.name定义了__get__方法和__set__方法,启动descriptor的调用; |
类调用:
1 | 发现XClass.name定义了__get__方法,启动descriptor转换 |
super 调用:
1 | super(XClass, obj).name()发现是__get__,启动 |
协议
格式:
1 | descr.__get__(self, obj, type=None) |
data descriptor(数据描述器):
对象同时定义了__get__
和__set__
,此时数据描述器的优先级 > 对象字典__dict__
中的属性。
not data descriptor(资料描述器):
对象仅仅定义__get__
,此时资料描述其的优先级小于__dict__
中的属性,见下面的”函数方法”例子/django 中 cached_property 例子.
实现
基于 descriptor 协议的实现由:property、bound/unbound 方法,staticmethod, classmethod
property:
1 | 对某个属性进行包装,并传入get/set/del等rewrite方法,实现指定功能。 |
函数和方法:
1 | Python的面向对象基于函数,其中descriptor将function和method衔接在一起,其中所有函数都是non-data descriptor. |
类方法和静态方法:
1 | non-data descriptor的函数绑定方法变种机制: |
序列化
序列化数据,需要配合Pickle, CPickle, 自定义协议,magic method来共同实现。
默认情况下,pickle支持内建数据结构
__getinitargs__
格式:__getinitargs__
(self)
说明:在序列化的同时调用__init__
方法,仅仅适用于老式类
__getnewargs__
格式:__getnewargs__
(self)
说明:在序列化的同时调用__new__
方法,适用于新类
__getstate__
格式:__getstate__
(self)
说明:定义当对象序列化后的返回状态,而不是使用__dict__
__setstate__
格式:__setstate__
(self, state)
说明:逆序列化时,对象的状态通过该函数进行和__getstate__
匹配的操作
魔术属性
参考:http://www.cnblogs.com/huxi/archive/2011/01/02/1924317.html
注意区别魔术方法和魔术属性的区别,不要搞混了。
__slots__
限定允许绑定的属性名称, 阻止在实例化类时为实例分配 dict, 其包含了当前可访问的属性, 相当于 C++中的成员变量声明. 在大量实例化的应用场景中,可以节省 30%的内容消耗, 命名元祖就是基于slots
和property
来实现的.
提出原因: 类实例化的时候, obj.__dict__
其实并不保存类属性和函数, 而每次实例化都会分配一个__dict__
, 存在空间浪费问题, 于是就是定义了__slots__
(一个元祖), 包含当前能够访问到的属性.
1 | class Student: |
该用法在很多公关项目的内部代码中广泛使用, 例如 flask, 另外__slots__
也可以在父子类中进行继承, 不过其有一定的规则:
- 子类未声明 slots, 不继承父类 slots, 此时子类可以随意赋值
- 子类声明 slots, 继承父类 slots, 此时子类的 slots 为父子两者之和
1 | class Parent: |
module
1 | __doc__:python doc string |
class
1 | __doc__: 类的doc string |
object
1 | __doc__: 类的doc string(通过dir可以查看,包含很多上文所述的魔术方法) |
built-in method and functions
1 | __doc__: 函数或者方法的文档 |
function
1 | __doc__: 函数文档 |
method
1 | __doc__: 方法docstring |
code
这里仅仅指代函数代码块,代码块–由类源代码或者简单语句代码编译得到,可用使用 func_code 获取:
1 | co_argcount:普通参数的总数,不包括*, ** |
frame
1 | frame--程序运行时函数调用栈中的某一帧 |
traceback
1 | tb_next: 下一个追踪对象 |