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 。反之,它应该返回一个二元组 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__直接用=会陷入无限递归的死循环

要使用下面这种格式

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