Python里的魔法方法

最近准备学习一波python反序列化,于是决定从python的魔法方法开始复习一下,python的魔法方法和php的还是有比较大的区别的,同时也用于以后的复习

参考:https://github.com/RafeKettler/magicmethods

构造方法

我们最常见的就是__init__,一般用于初始化对象,当然当我们建立一个新的对象,最早调用的是__new__,而不是__init__.而__del__则会在对象生命周期结束时被调用

__new__(cls,[...])

只取下cls参数,然后把其他参数给__init__

__init__(self,[...])

__del__(self)

clsself的区别简单来说就是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)

    实现按位异或运算符 ^

反射算数运算符

反射可以理解为一种逆的思维,下面以加法为例,下面是对象的加法

1
object + other

而反射算数运算符则是

1
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 。反之,它应该返回一个二元组 selfother ,这两者均已被转换成相同的类型。

访问控制

和其他语言不太一样的是,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__直接用=会陷入无限递归的死循环

要使用下面这种格式

1
2
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] 的方式进行赋值操作时的行为。它是可变容器类型必须实现的一个方法,同样应该在合适的时候产生 KeyErrorTypeError 异常。

  • _iter_(self, key)

    它应该返回当前容器的一个迭代器。迭代器以一连串内容的形式返回,最常见的是使用 iter() 函数调用,以及在类似 for x in container: 的循环中被调用。迭代器是他们自己的对象,需要定义 _iter_ 方法并在其中返回自己。

  • _reversed_(self)

    定义了对容器使用 reversed() 内建函数时的行为。它应该返回一个反转之后的序列。当你的序列类是有序时,类似列表和元组,再实现这个方法,

  • _contains_(self, item)

    _contains_ 定义了使用 innot 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的字典元素的迭代器(可选);

    • _reduceex(self)

      _reduceex 的存在是为了兼容性。如果它被定义,在pickle时_reduceex 会代替 _reduce_ 被调用。 _reduce_ 也可以被定义,用于不支持 _reduceex 的旧版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_