Python里的魔法方法
最近准备学习一波python反序列化,于是决定从python的魔法方法开始复习一下,python的魔法方法和php的还是有比较大的区别的,同时也用于以后的复习
参考:https://github.com/RafeKettler/magicmethods
构造方法
我们最常见的就是__init__
,一般用于初始化对象,当然当我们建立一个新的对象,最早调用的是__new__
,而不是__init__
.而__del__
则会在对象生命周期结束时被调用
__new__(cls,[...])
只取下cls参数,然后把其他参数给__init__
__init__(self,[...])
__del__(self)
cls
和self
的区别简单来说就是self
是具体示例对象的本身,而cls
则是对应于类的本身
操作符
使用Python魔法方法的一个巨大优势就是可以构建一个拥有Python内置类型行为的对象。这意味着你可以避免使用非标准的、丑陋的方式来表达简单的操作。
例如当我们定义了__eq__
以后,那么这个类的==就会调用上面我们定义的那个魔法函数,类似于C++的重载运算符。
比较操作符
-
*_cmp_(self, other)*
*_cmp_* 是所有比较魔法方法中最基础的一个,它实际上定义了所有比较操作符的行为(<,==,!=,等等),但是它可能不能按照你需要的方式工作(例如,判断一个实例和另一个实例是否相等采用一套标准,而与判断一个实例是否大于另一实例采用另一套)。 *__cmp__* 应该在 *self < other* 时返回一个负整数,在*self == other* 时返回0,在 *self > other* 时返回正整数。最好只定义你所需要的比较形式,而不是一次定义全部。 如果你需要实现所有的比较形式,而且它们的判断标准类似,那么 *__cmp__* 是一个很好的方法,可以减少代码重复,让代码更简洁。
-
*_eq_`(self, other)*
定义等于操作符(==)的行为。
-
*_ne_(self, other)*
定义不等于操作符(!=)的行为。
-
*_lt_(self, other)*
定义小于操作符(<)的行为。
-
*_gt_(self, other)*
定义大于操作符(>)的行为。
-
*_le_(self, other)*
定义小于等于操作符(<)的行为。
-
*_ge_(self, other)*
定义大于等于操作符(>)的行为。
数值操作符
主要有五类:一元操作符,常见算数操作符,反射算数操作符(后面会涉及更多),增强赋值操作符,和类型转换操作符。
常见算数操作符和增强赋值运算符基本和C++的重载运算符一致,其余则是python比C++多的
一元操作符
顾名思义,只有一个参数的操作符
-
*_pos_(self)*
实现取正操作,例如 +some_object。
-
*_neg_(self)*
实现取负操作,例如 -some_object。
-
*_abs_(self)*
实现内建绝对值函数 abs() 操作。
-
*_invert_(self)*
实现取反操作符 ~。
-
_round_(self, n)
实现内建函数 round() ,n 是近似小数点的位数。
-
*_floor_(self)*
实现 math.floor() 函数,即向下取整。
-
*_ceil_(self)*
实现 math.ceil() 函数,即向上取整。
-
*_trunc_(self)*
实现 math.trunc() 函数,即距离零最近的整数。
常见算数操作符
主要是二元操作符和一些函数
-
*_add_(self, other)*
实现加法操作。
-
*_sub_(self, other)*
实现减法操作。
-
*_mul_(self, other)*
实现乘法操作。
-
*_floordiv_(self, other)*
实现使用 // 操作符的整数除法。
-
*_div_(self, other)*
实现使用 / 操作符的除法。
-
*_truediv_(self, other)*
实现 true 除法,这个函数只有使用 *from _future_ import division* 时才有作用。
-
*_mod_(self, other)*
实现 % 取余操作。
-
*_divmod_(self, other)*
实现 divmod 内建函数。
-
*_pow_*
实现 **** 操作符。
-
*_lshift_(self, other)*
实现左移位运算符 « 。
-
*_rshift_(self, other)*
实现右移位运算符 » 。
-
*_and_(self, other)*
实现按位与运算符 & 。
-
*_or_(self, other)*
实现按位或运算符 | 。
-
*_xor_(self, other)*
实现按位异或运算符 ^ 。
反射算数运算符
反射可以理解为一种逆的思维,下面以加法为例,下面是对象的加法
object + other
而反射算数运算符则是
other+object
所有反射运算符魔法方法和它们的常见版本做的工作相同,只不过是处理交换连个操作数之后的情况。绝大多数情况下,反射运算和正常顺序产生的结果是相同的,所以很可能你定义 __radd__
时只是调用一下 __add__
。
-
*_radd_(self, other)*
实现反射加法操作。
-
*_rsub_(self, other)*
实现反射减法操作。
-
*_rmul_(self, other)*
实现反射乘法操作。
-
*_rfloordiv_(self, other)*
实现使用 // 操作符的整数反射除法。
-
*_rdiv_(self, other)*
实现使用 / 操作符的反射除法。
-
*_rtruediv_(self, other)*
实现 true 反射除法,这个函数只有使用 from future import division时才有作用。
-
*_rmod_(self, other)*
实现 % 反射取余操作符。
-
*_rdivmod_(self, other)*
实现调用 divmod(other, self) 时 divmod 内建函数的操作。
-
*_rpow_*
实现 **** 反射操作符。
-
*_rlshift_(self, other)*
实现反射左移位运算符 « 的作用。
-
*_rshift_(self, other)*
实现反射右移位运算符 » 的作用。
-
*_rand_(self, other)*
实现反射按位与运算符 & 。
-
*_ror_(self, other)*
实现反射按位或运算符 | 。
-
*_rxor_(self, other)*
实现反射按位异或运算符 ^ 。
增强赋值操作符
增强赋值操作符其实就是那些+=,-=那些
-
*_iadd_(self, other)*
实现加法赋值操作。
-
*_isub_(self, other)*
实现减法赋值操作。
-
*_imul_(self, other)*
实现乘法赋值操作。
-
*_ifloordiv_(self, other)*
实现使用 //= 操作符的整数除法赋值操作。
-
*_idiv_(self, other)*
实现使用 /= 操作符的除法赋值操作。
-
*_itruediv_(self, other)*
实现 true 除法赋值操作,这个函数只有使用 from future import division 时才有作用。
-
*_imod_(self, other)*
实现 %= 取余赋值操作。
-
*_ipow_*
实现 **= 操作。
-
*_ilshift_(self, other)*
实现左移位赋值运算符 «= 。
-
*_irshift_(self, other)*
实现右移位赋值运算符 »= 。
-
*_iand_(self, other)*
实现按位与运算符 &= 。
-
*_ior_(self, other)*
实现按位或赋值运算符 | 。
-
*_ixor_(self, other)*
实现按位异或赋值运算符 ^= 。
类型转换操作符
用于实现类似 float() 的内建类型转换函数的操作
-
*_int_(self)*
实现到int的类型转换。
-
*_long_(self)*
实现到long的类型转换。
-
*_float_(self)*
实现到float的类型转换。
-
*_complex_(self)*
实现到complex的类型转换。
-
*_oct_(self)*
实现到八进制数的类型转换。
-
*_hex_(self)*
实现到十六进制数的类型转换。
-
*_index_(self)*
实现当对象用于切片表达式时到一个整数的类型转换。如果你定义了一个可能会用于切片操作的数值类型,你应该定义 *_index_*。
-
*_trunc_(self)*
当调用 math.trunc(self) 时调用该方法, *_trunc_* 应该返回 *self* 截取到一个整数类型(通常是long类型)的值。
-
*_coerce_(self)*
该方法用于实现混合模式算数运算,如果不能进行类型转换, *_coerce_* 应该返回 *None* 。反之,它应该返回一个二元组 *self* 和 *other* ,这两者均已被转换成相同的类型。
访问控制
和其他语言不太一样的是,python没有真正的封装,不过python有自己的访问控制,和其他语言不一样的是,这个访问控制不是显式的,而是通过魔术函数来进行访问控制。
- *_getattr_(self, name)*
当用户试图访问一个根本不存在(或者暂时不存在)的属性时,你可以通过这个魔法方法来定义类的行为。这个可以用于捕捉错误的拼写并且给出指引,使用废弃属性时给出警告(如果你愿意,仍然可以计算并且返回该属性),以及灵活地处理AttributeError。只有当试图访问不存在的属性时它才会被调用,所以这不能算是一个真正的封装的办法。
- *_setattr_(self, name, value)*
和 *_getattr_* 不同, *__setattr__* 可以用于真正意义上的封装。它允许你自定义某个属性的赋值行为,不管这个属性存在与否,也就是说你可以对任意属性的任何变化都定义自己的规则。然后,一定要小心使用 *__setattr__* ,这个列表最后的例子中会有所展示。
- *_delattr_(self, name)*
这个魔法方法和 *_setattr_* 几乎相同,只不过它是用于处理删除属性时的行为。和 *_setattr__* 一样,使用它时也需要多加小心,防止产生无限递归(在*__delattr__* 的实现中调用 *del self.name* 会导致无限递归)。
- *_getattribute_(self, name)*
__getattribute__
看起来和上面那些方法很合得来,但是最好不要使用它。*_getattribute_* 只能用于新式类。在最新版的Python中所有的类都是新式类,在老版Python中你可以通过继承 *object* 来创建新式类。 *__getattribute__* 允许你自定义属性被访问时的行为,它也同样可能遇到无限递归问题(通过调用基类的*__getattribute__* 来避免)。 *__getattribute__* 基本上可以替代 *__getattr__* 。只有当它被实现,并且显式地被调用,或者产生 *AttributeError* 时它才被使用。 这个魔法方法可以被使用(毕竟,选择权在你自己),我不推荐你使用它,因为它的使用范围相对有限(通常我们想要在赋值时进行特殊操作,而不是取值时),而且实现这个方法很容易出现Bug。
注意,自定义__setattr__
时赋值要用__dict__
而不能直接用=,因为=调用的就是__setattr__
直接用=会陷入无限递归的死循环
要使用下面这种格式
def __setattr__(self, name, value):
self.__dict__[name] = value # 使用 __dict__ 进行赋值
自定义序列
Python中有许多办法可以让你的Python类表现得像是内建序列类型(字典,元组,列表,字符串等)。
相关知识
在python中实现自定义容器类型需要用到一些协议,协议类似某些语言中的接口,里面包含的是一些必须实现的方法。在Python中,协议完全是非正式的,也不需要显式的声明,事实上,它们更像是一种参考标准。
实现一个不可变容器,你需要定义__len__
和__getitem__
而可变容器则不仅需要上面的两个方法,还需要定义__setitem__
和 __delitem__
如果你还想要你的对象可以进行迭代,那么你还需要定义__iter__
上面提到各个魔法方法详情可见于下面
容器背后的魔法方法
-
*_len_(self)*
返回容器的长度,可变和不可变类型都需要实现。
-
*_getitem_(self, key)*
定义对容器中某一项使用 self[key] 的方式进行读取操作时的行为。这也是可变和不可变容器类型都需要实现的一个方法。它应该在键的类型错误式产生TypeError 异常,同时在没有与键值相匹配的内容时产生 KeyError 异常。
-
*_setitem_(self, key)*
定义对容器中某一项使用 self[key] 的方式进行赋值操作时的行为。它是可变容器类型必须实现的一个方法,同样应该在合适的时候产生 KeyError 和TypeError 异常。
-
*_iter_(self, key)*
它应该返回当前容器的一个迭代器。迭代器以一连串内容的形式返回,最常见的是使用 iter() 函数调用,以及在类似 for x in container: 的循环中被调用。迭代器是他们自己的对象,需要定义 *_iter_* 方法并在其中返回自己。
-
*_reversed_(self)*
定义了对容器使用 reversed() 内建函数时的行为。它应该返回一个反转之后的序列。当你的序列类是有序时,类似列表和元组,再实现这个方法,
-
*_contains_(self, item)*
*_contains_* 定义了使用 *in* 和 *not in* 进行成员测试时类的行为。你可能好奇为什么这个方法不是序列协议的一部分,原因是,如果 *__contains__* 没有定义,Python就会迭代整个序列,如果找到了需要的一项就返回 *True* 。
-
*_missing_(self ,key)*
*_missing_* 在字典的子类中使用,它定义了当试图访问一个字典中不存在的键时的行为(目前为止是指字典的实例,例如我有一个字典 *d* , *“george”* 不是字典中的一个键,当试图访问 *d[“george’]* 时就会调用*d.__missing__(“george”)* )
eg:
class FunctionalList: def __init__(self, values=None): if values is None: self.values = [] else: self.values = values def __len__(self): return len(self.values) def __getitem__(self, key): return self.values[key] def __setitem__(self, key, value): self.values[key] = value def __delitem__(self, key): del self.values[key] def __iter__(self): return iter(self.values) def __reversed__(self): return reversed(self.values) def append(self, value): self.values.append(value) def head(self): return self.values[0] def tail(self): return self.valuse[1:] def init(self): return self.values[:-1] def last(self): return self.values[-1] def drop(self, n): return self.values[n:] def take(self, n): return self.values[:n]
Pickling
下面让我们来关注和python序列相关的内容,也就是Pickling,当你想存储一个对象稍后再取出读取时,Pickling会显得十分有用
Pickling不仅仅有自己的模块( pickle ),还有自己的协议和魔法方法。首先,我们先来简要的介绍一下如何pickle已存在的对象类型
简单示例
例如我们要将一个字典类型储存起来,之后再将其取出来使用,我们可以把它写入一个文件,但是使用exec()或处理文件输入的方法并不可靠,如果使用纯文本来进行数据储存,数据可能会被破坏或者修改,甚至会被写入恶意代码,所以这个时候就要使用我们的pickle了
类似于序列化过程
import pickle data = {'foo': [1,2,3], 'bar': ('Hello', 'world!'), 'baz': True} jar = open('data.pkl', 'wb') pickle.dump(data, jar) # 将pickle后的数据写入jar文件 jar.close()
反pickle过程
import pickle pkl_file = open('data.pkl', 'rb') # 与pickle后的数据连接 data = pickle.load(pkl_file) # 把它加载进一个变量 print data pkl_file.close()
print出的data就是我们之前的data
Pickle对象
Pickle不仅可以用于内建联系,任何遵守pickle协议的类都可以被pickle
Pickle协议有四个可选方法,可以让类自定义它们的行为
-
*_getinitargs_(self)*
如果你想让你的类在反pickle时调用 *_init_* ,你可以定义*__getinitargs__(self)* ,它会返回一个参数元组,这个元组会传递给*__init__* 。注意,这个方法只能用于旧式类。
-
*_getnewargs_(self)*
对新式类来说,你可以通过这个方法改变类在反pickle时传递给 *_new_* 的参数。这个方法应该返回一个参数元组。
-
*_getstate_(self)*
你可以自定义对象被pickle时被存储的状态,而不使用对象的 *_dict_* 属性。 这个状态在对象被反pickle时会被 *__setstate__* 使用。
-
*_setstate_(self)*
当一个对象被反pickle时,如果定义了 *_setstate_* ,对象的状态会传递给这个魔法方法,而不是直接应用到对象的 *__dict__* 属性。这个魔法方法和*__getstate__* 相互依存:当这两个方法都被定义时,你可以在Pickle时使用任何方法保存对象的任何状态。
-
*_reduce_(self)*
当定义扩展类型时(也就是使用Python的C语言API实现的类型),如果你想pickle它们,你必须告诉Python如何pickle它们。 *_reduce_* 被定义之后,当对象被Pickle时就会被调用。它要么返回一个代表全局名称的字符串,Pyhton会查找它并pickle,要么返回一个元组。这个元组包含2到5个元素,其中包括:一个可调用的对象,用于重建对象时调用;一个参数元素,供那个可调用对象使用;被传递给 *__setstate__* 的状态(可选);一个产生被pickle的列表元素的迭代器(可选);一个产生被pickle的字典元素的迭代器(可选);
-
*_reduce_ex_(self)*
*_reduce_ex_* 的存在是为了兼容性。如果它被定义,在pickle时*__reduce_ex__* 会代替 *__reduce__* 被调用。 *__reduce__* 也可以被定义,用于不支持 *__reduce_ex__* 的旧版pickle的API调用
·
pickle对象的例子
会记住它的值曾经是什么,以及那些值是什么时候赋给它的,不过每次pickle时它都会变成空白,因为当前的值不会被储存
import time class Slate: '''存储一个字符串和一个变更日志的类 每次被pickle都会忘记它当前的值''' def __init__(self, value): self.value = value self.last_change = time.asctime() self.history = {} def change(self, new_value): # 改变当前值,将上一个值记录到历史 self.history[self.last_change] = self.value self.value = new_value) self.last_change = time.asctime() def print_change(self): print 'Changelog for Slate object:' for k,v in self.history.items(): print '%s\t %s' % (k,v) def __getstate__(self): # 故意不返回self.value或self.last_change # 我们想在反pickle时得到一个空白的slate return self.history def __setstate__(self): # 使self.history = slate,last_change # 和value为未定义 self.history = state self.value, self.last_change = None, None
如何调用魔法方法
魔法方法 什么时候被调用 解释 new(cls [,…]) instance = MyClass(arg1, arg2) __new__在实例创建时调用 init(self [,…]) instance = MyClass(arg1,arg2) __init__在实例创建时调用 cmp(self) self == other, self > other 等 进行比较时调用 pos(self) +self 一元加法符号 neg(self) -self 一元减法符号 invert(self) ~self 按位取反 index(self) x[self] 当对象用于索引时 nonzero(self) bool(self) 对象的布尔值 getattr(self, name) self.name #name不存在 访问不存在的属性 setattr(self, name) self.name = val 给属性赋值 _delattr(self, name) del self.name 删除属性 getattribute(self,name) self.name 访问任意属性 getitem(self, key) self[key] 使用索引访问某个元素 setitem(self, key) self[key] = val 使用索引给某个元素赋值 delitem(self, key) del self[key] 使用索引删除某个对象 iter(self) for x in self 迭代 contains(self, value) value in self, value not in self 使用in进行成员测试 call(self [,…]) self(args) “调用”一个实例 enter(self) with self as x: with声明的上下文管理器 exit(self, exc, val, trace) with self as x: with声明的上下文管理器 getstate(self) pickle.dump(pkl_file, self) Pickling setstate(self) data = pickle.load(pkl_file) Pickling python2和3中的区别
在这里,我们记录了几个在对象模型方面 Python 3 和 Python 2.x 之间的主要区别。
- Python 3中string和unicode的区别不复存在,因此*__unicode__*被取消了,*__bytes__*加入进来(与Python 2.7 中的 *__str__*和 *__unicode__*行为类似),用于新的创建字节数组的内建方法。
- Python 3中默认除法变成了 true 除法,因此*__div__*被取消了。
- *_coerce_* 被取消了,因为和其他魔法方法有功能上的重复,以及本身行为令人迷惑。
- *_cmp_* 被取消了,因为和其他魔法方法有功能上的重复。
- *_nonzero_* 被重命名成 *__bool__*
-