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)
实现按位异或运算符 ^ 。
反射算数运算符
反射可以理解为一种逆的思维,下面以加法为例,下面是对象的加法
|
|
而反射算数运算符则是
|
|
所有反射运算符魔法方法和它们的常见版本做的工作相同,只不过是处理交换连个操作数之后的情况。绝大多数情况下,反射运算和正常顺序产生的结果是相同的,所以很可能你定义 __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__
直接用=会陷入无限递归的死循环
要使用下面这种格式
|
|
自定义序列
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
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了
类似于序列化过程
1 2 3 4 5 6 7 8
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过程
1 2 3 4 5 6
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时它都会变成空白,因为当前的值不会被储存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
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_
-