UP | HOME

Python 魔法方法

目录

1 简介

Python 中, 你可以通过定义一些特殊的方法来控制一个对象的行为。

这些方法的名字常用名为 魔法方法魔术方法.

当然了, 官方文档的名字更加直接, 叫做 Special method.

这篇博客主要借鉴 Python 魔术方法指南部分 魔法方法进行总结。

2 构造和初始化

Python 一个对象实例化到销毁调用的方法的顺序是:

__new__() ==> __init__() ==> ... ==> __del__()

因为最常用的方法是 __init__(), 所以我一直认为最先调用的方法应该是 __init__(), 但结果居然还有一个 __new__(). 没人气的方法 @_@.

__new__(cls[,…]))
对象实例化的时候所调用的第一个方法, 它的第一个参数是这个类,其他的参数是用来直接传递给 __init__() 方法。
__init__(self[, …])
类的初始化方法。 可以说是最常用的方法了。 调用构造函数时的参数就是传给它的。
__del__(self)
当实例即将被销毁时调用, 也被叫做析构函数。 没有参数.
class Test(object):
    def __init__(self):
        print("Hello World !")

    def __del__(self):
        print("Goodbye World QAQ")

T = Test()
del T

3 比较和数值运算

一个有趣的例子:

# one list
>>> l = [0]

# one tuple
>>> t = (1, )

# list + tuple ?
>>> l + t
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate list (not "tuple") to list

# list += tuple ?
>>> l += t
[0, 1]

一个列表直接加一个元组会出错, 而 += 没有问题…

当时我很疑惑, 有大佬给出了解答: l + t 对应的是 l.__add__(t) ,而 l += t 对应的是 l.__iadd__(t) ,实现可以是不同的.

当然, 大佬还说了这样的代码学习可以,就不要在实际操作中用了。

3.1 用于比较的魔法方法

__cmp__(self, other)
最基本的用于比较的魔法方法, 现了所有的比较符号 (<,==,!=,etc.). 如果 self < other 应该返回一个 负数, 如果 self == other 应该返回 0, 如果 self > other 应该返回一个 正数.
__eq__(self, other)
定义了 == 行为, 相等返回 True, 反之返回 False.
__ne__(self, other)
定义了 != 行为, 不等返回 True, 反之返回 False.
__lt__(self, other)
定义了 < 行为, 小于返回 True, 反之返回 False.
__gt__(self, other)
定义了 > 行为, 大于返回 True, 反之返回 False.
class Word(str):
'''存储单词的类,定义比较单词的几种方法'''

    def __new__(cls, word):
        # 注意我们必须要用到__new__方法,因为str是不可变类型
        # 所以我们必须在创建的时候将它初始化
        if ' ' in word:
            print "Value contains spaces. Truncating to first space."
            word = word[:word.index(' ')] #单词是第一个空格之前的所有字符
        return str.__new__(cls, word)

    def __gt__(self, other):
        return len(self) > len(other)
    def __lt__(self, other):
        return len(self) < len(other)
    def __ge__(self, other):
        return len(self) >= len(other)
    def __le__(self, other):
        return len(self) <= len(other)

这里就直接可耻的盗用 指南 的例子了。

3.2 数值处理的魔法方法

  • 一元操作符和函数
    __pos__(self)
    实现正号的特性, 如 +object
    __neg__(self)
    实现负号的特性, 如 -object
    __abs__(self)
    实现内置 abs() 函数的特性, 如 abs(object)
    __invert__(self)
    实现 ~ 符号的特性, 参考文章: Bitwise operation
  • 普通算数操作符
    __add__(self, other)
    实现加法, 如 A + B
    __sub__(self, other)
    实现减法, 如 A - B
    __mul__(self, other)
    实现乘法, 如 A * B
    __floordiv__(self, other)
    实现 // 除, 如 A // B
    __div__(self, other)
    实现 / 除, 如 A / B
    __truediv__(self, other)

    实现真除法, 需要:

    from __future__ import division
    
    __mod__(self, other)
    实现取模运算, 如 A % B
    __divmod___(self, other)
    实现内置函数 divmod() 运算, 如 divmod(obj)
    __pow___(self, other[, modulo])
    实现使用 ** 的指数运算, 如 A**2
    __lshift__(self, other)
    实现使用 << 的按位左移运算, 如 A << B
    __rshift__(self, other)
    实现使用 >> 的按位右移运算, 如 A >> B
    __and__(self, other)
    实现使用 & 的按位与运算, 如 A & B
    __or__(self, other)
    实现使用 | 的按位或运算, 如 A | B
    __xor__(self, other)
    实现使用 ^ 的按位异或运算, 如 A ^ B
  • 反运算

    指南给出的反运算的解释:

    一个普通的加法运算: some_object + other
    
    对应的反运算: other + some_object
    

    意思就是把操作数调了个位置, 大多数情况下,反运算的结果是与普通运算相同的。

    具体的实现和 普通算数操作符 的实现差不多, 只需要在对应的运算前面加上一个 r 就可以了。

    如: __add__() 对应 __radd__().

    Update 2018-05-14: 突然明白反运算是什么意思了:

    • self + other 就是正运算
    • other + self 就是反运算
  • 增量赋值

    这个就是比较熟悉的运算了, 就是类似于 A += B 的运算, 实现方式和 普通算数操作符 类似,只需要在对应的运算前面加上一个 i 就可以了。

    如: __add__() 对应 __iadd__().

    实际操作就是对应的运算符紧跟一个等号 =.

    如: +=, >>=, **=

4 类型转化

可以通过一些 魔法方法 来实现内置类型类型转换, 注意, 是转换为 内置类型.

__int__(self)
实现整形的强制转换
__long__(self)
实现长整形的强制转换, 当然, 这应该是 Python2
__float__(self)
实现浮点型的强制转换
__complex__(self)
实现复数的强制转换
__oct__(self)
实现八进制的强制转换
__hex__(self)
实现二进制的强制转换
__index__(self)
当对象是被应用在 切片表达式 中时,实现整形强制转换
__trunc__(self)
当使用 math.trunc(self) 的时候被调用, 应该返回数值被截取成整形的值
__coerce__(self, other)
实现混合模式算数, 也是 Python2

转换成什么类型就返回什么类型的值。

5 表现类

__str__(self)
使用 str(obj) 会调用这个方法
__repr__(self)
使用 repr(obj) 会调用这个方法
__unicode(self)
使用 unicode(obj) 会调用这个方法, 当然这也是 Python2
__hash__(self)
使用 hash(obj) 会调用这个方法, 应该返回一个 整型
__nonzero(self)
使用 bool(obj) 会调用这个方法, 应该返回 TrueFalse. 在 Python3 中改为了 __bool__().

6 属性访问控制

作为动态语言, Python 一个对象的属性的访问是很开放的。 如果需要对属性的访问进行控制,就可以使用这一部分的 魔法方法.

__getattr__(self, name)
获取不存在的属性的值时会调用这个方法, 所以在这个方法内可以获取正常属性的值。
__getattribute__(self, name)

获取对象的属性时调用, 不管属性存不存在。 同时如果定义了这个方法, __getattr__ 将不起作用, 除非显示调用或引发 AttributeError.

由于获取任意属性都将调用这一方法, 所以如果在这个方法内获取属性将导致 无限递归.

解决这一问题的方法是把获取属性的方法指向一个更高的 超类:

class ClassA:
    def __getattribute__(self, item):
        return super().__getattribute__(self, item)
__setattr__(self, name, value)

定义设置对象的属性时的行为, 即 obj.xxx = xxx 时会调用这个方法

使用这个方法需要注意的一点:

def __setattr__(self, name, value):
    self.name = value
    # 每当属性被赋值的时候, ``__setattr__()`` 会被调用,这样就造成了递归调用
    # 这意味这会调用 ``self.__setattr__('name', value)`` ,每次方法会调用自己。这样会造成程序崩溃

def __setattr__(self, name, value):
    # 通过这种方式来避免
    self.__dict__[name] = value  # 给类中的属性名分配值

设置 实例 属性会调用这个方法, 包括在 __init__ 中设置属性。 但类属性不受影响。

__delattr__(self, name)
定义删除对象的属性时的行为, 即 del obj.xxx 时会调用这个方法

对于这一部分的 魔法方法, 每个 对象__dict__ 属性应该是一个很重要的内容, 有兴趣可以了解一下 ☞ object.__dict__.

7 创建定制的序列

Python 中的序列有 可变不可变 的区分, 比如 tuplelist.

通过魔法方法的定义来控制创建的序列的类型:

  • 不可变: 只能定义 __len____getitem__.
  • 可变: 需要定义 __setitem____delitem__.
  • 可迭代: 需要定义 __iter__ 返回一个迭代器

这些魔法方法的具体作用:

__len__(self)
使用 len(obj) 的时候调用, 返回序列的长度
__getitem__(self, key)
使用 obj[key] 时调用, 返回序列指定键的值
__setitem__(self, key, value)
使用 obj[key] = value 时调用, 设置序列指定键的值
__delitem__(self, key)
使用 del obj[key] 时调用, 删除序列指定条目
__iter__(self)
返回迭代器, iter(obj)
__reversed__(self)
使用 reversed(obi) 时调用, 反转序列
__contains__(self, item)
使用 item in objitem not in obj 时调用, 检测成员是否存在
__concat__(self, other)
连接两个序列的时候调用

8 反射

通过魔法方法控制使用 isinstanceissubclass 的反射行为:

__instancecheck__(self, instance)
检查一个实例是不是定义的类的实例, 对应 isinstance
__subclasscheck__(self, subclass)
检查一个类是不是定义的类的子类, 对应 issubclass

9 可以调用的对象

如果你希望可以向调用函数那样调用你的 对象, 那么你可以定义那个对象的 __call__ 方法。

__call__(self, [args…])
使用 obj(...) 时调用
class Test(object):
    def __init__(self):
        self.val = 10

    def __call__(self):
        return self.val

t = Test()
t()

10 上下文管理器

Python 中使用 with 可以写出更加简洁高效的代码, 而能够使用 with 的对象需要支持上下文管理器。

上下文管理器需要两个方法: __enter____exit__.

with expression [as variable]:
    with-block
__enter__(self)
这个方法在进入 with-block 之前调用, 返回值被 with 的目标或 as 后的名字绑定
__exit__(self, exception_type, exception_value, traceback)
这个方法在退出 with-block 时调用
class Closer(object):
    '''通过with语句和一个close方法来关闭一个对象的会话管理器'''

    def __init__(self, obj):
    self.obj = obj

    def __enter__(self):
        return self.obj # bound to target

    def __exit__(self, exception_type, exception_val, trace):
        try:
            self.obj.close()
        except AttributeError: # obj isn't closable
            print 'Not closable.'
            return True # exception handled successfully

11 更多的方法

Python 的魔法方法还有不少, Python2Python3 在这方面也有一定的区别。

12 参考链接

版权声明:本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可