魔法方法#

1. 运算符相关#

1.1. 常规运算#

魔法方法

运算符

增量运算

反向运算

__add__

+

__iadd__

__radd__

__sub__

-

__isub__

__rsub__

__mul__

*

__imul__

__rmul__

__truediv__

/

__itruediv__

__rtruediv__

__floordiv__

//

__ifloordiv__

__rfloordiv__

__pow__

**

__ipow__

__rpow__

__mod__

%

__imod__

__rmod__

__divmod__

__rdivmod__

__matmul__

@

__round__

1.2. 位运算#

魔法方法

运算符

增量运算

反向运算

__invert__

~

__lshift__

<<

__ilshift__

__rlshift__

__rshift__

>>

__irshift__

__rrshift__

__and__

&

__iand__

__rand__

__or__

__ior__

__ror__

__xor__

^

__ixor__

__rxor__

1.3. 比较与正负#

在标准库中有两例 x != +x 的情况。

  • decimal.Decimal

  • collections.Counter

魔法方法

运算符

魔法方法

运算符

__neg__

-

__pos__

+

__lt__

<

__gt__

>

__le__

<=

__ge__

>=

__eq__

==

__ne__

!=

2. 转换#

2.1. 字符串转换#

魔法方法

类方法

__repr__

__str__

str()

__format__

format()

__bytes__

bytes()

2.2. 数值转换#

魔法方法

类方法

__abs__

abs()

__bool__

bool()

__complex__

complex()

__int__

int()

__float__

float()

__index__

index()

__hash__

hash()

3. 容器相关#

Python 的序列协议只需要 __len____getitem__ 两个方法。任何类只要使用标准的签名和语义实现了这两个方法,就能用在任何期待序列的地方。

需要定制 list、dict 或 str 类型时,子类化 UserListUserDictUserString 更简单。若所需的行为与内置类型区别很大,更容易的做法是,子类化 collections.abc 模块中相应的抽象基类。

3.1. 构建#

魔法方法

类方法

__getitem__

__setitem__

seq[i:j] = values

__delitem__

__len__

len()

__contains__

in

Card = namedtuple('Card', ['rank', 'suit'])

class FrenchDeck:
    ranks = [str(n) for n in range(2, 11)] + list('JQKA')
    suits = 'spades diamonds clubs hearts'.split()

    def __init__(self):
        self._cards = [Card(rank, suit) for suit in self.suits
                                        for rank in self.ranks]

    def __len__(self):
        return len(self._cards)

    def __getitem__(self, position):
        return self._cards[position]


len(deck) # 52
deck[-1] # Card(rank='A', suit='hearts')

3.2. 猴子补丁#

FrenchDeck 只实现了不可变的序列协议。可变的序列还必须提供 __setitem__ 方法。下面这种技术叫猴子补丁:在运行时修改类或模块,而不改动源码。

from random import shuffle


def set_card(deck, position, card):
    deck._cards[position] = card

FrenchDeck.__setitem__ = set_card
shuffle(deck)

猴子补丁的名声不太好。若滥用,会导致系统难以理解和维护。补丁通常与目标紧密耦合,因此很脆弱。另一个问题是,打了猴子补丁的两个库可能相互牵绊,因为第二个库可能撤销了第一个库的补丁。

3.3. 元素和变量#

魔法方法

类方法

__not__

not

__missing__

对含有 key 依赖的缺省值的字典,可使用 __missing__ 构建类。

class Pictures(dict):
    def __missing__(self, key):
        value = open_picture(key)
        self[key] = value
        return value

pictures = Pictures()
handle = pictures[path]
handle.seek(0)
image_data = handle.read()

4. 类相关#

4.1. 实例#

魔法方法

类方法

__new__

__init__

__del__

4.2. 属性#

魔法方法

调用场景

__getattr__

访问一个缺省属性时

__get__

__setattr__

__set__

__delattr__

__delete__

__getattribute__

访问任何属性时

__dir__

5. 函数相关#

魔法方法

类型

__annotations__

dict

__await__

__call__

method-wrapper

__closure__

tuple

__code__

code

__defaults__

tuple

__kwdefaults__

dict

__globals__

dict

__name__

str

__qualname__

str

只要某个对象重写了 __call__() 方法,则这个对象就是可调用对象(callable)。

class Student:
    self.name = name

    def __call__(self):
        print(f'My name is {self.name}.')

s = Student('Michael')
s() # My name is Michael.

6. 抽象基类#

6.1. 自定义字典#

UserDict 并不是 dict 的子类,但是 UserDict 有一个叫作 data 的属性,是 dict 的实例,这个属性实际上是 UserDict 最终存储数据的地方。

UserDict 的子类就能在实现 __setitem__ 的时候避免不必要的递归,也可以让 __contains__ 里的代码更简洁。

import collections


class StrKeyDict(collections.UserDict):

    def __missing__(self, key):
        if isinstance(key, str):
            raise KeyError(key)
        return self[str(key)]

    def __contains__(self, key):
        return str(key) in self.data

    def __setitem__(self, key, item):
        self.data[str(key)] = item

6.2. 可变序列#

import collections

Card = collections.namedtuple('Card', ['rank', 'suit'])


class FrenchDeck2(collections.MutableSequence):
    ranks = [str(n) for n in range(2, 11)] + list('JQKA')
    suits = 'spades diamonds clubs hearts'.split()

    def __init__(self):
        self._cards = [
            Card(rank, suit) for suit in self.suits for rank in self.ranks
        ]

    def __len__(self):
        return len(self._cards)

    def __getitem__(self, position):
        return self._cards[position]

    # 支持洗牌
    def __setitem__(self, position, value):
        self._cards[position] = value

    # 继承 MutableSequence 的类必须实现
    def __delitem__(self, position):
        del self._cards[position]

    def insert(self, position, value):
        self._cards.insert(position, value)

6.3. 自定义#

import abc


class Tombola(abc.ABC):

    # 抽象方法使用 @abstractmethod 装饰器标记,定义体中通常只有文档字符串
    @abc.abstractmethod
    def load(self, iterable):
        """从可迭代对象中添加元素。"""

    @abc.abstractmethod
    def pick(self):
        """随机删除元素,然后将其返回。
        若实例为空,这个方法应该抛出`LookupError`。
        """

    def loaded(self):
        """若至少有一个元素,返回`True`,否则返回`False`。"""
        return bool(self.inspect())

    def inspect(self):
        """返回一个有序元组,由当前元素构成。"""
        items = []
        while True:
            try:
                items.append(self.pick())
            except LookupError:
                break
        self.load(items)
        return tuple(sorted(items))

6.4. 子类#

from random import randrange


class LotteryBlower(Tombola):

    def __init__(self, iterable):
        self._balls = list(iterable)

    def load(self, iterable):
        self._balls.extend(iterable)

    def pick(self):
        try:
            position = randrange(len(self._balls))
        except ValueError:
            raise LookupError('pick from empty LotteryBlower')
        return self._balls.pop(position)

    def loaded(self):
        return bool(self._balls)

    def inspect(self):
        return tuple(sorted(self._balls))

6.5. 虚拟子类#

虚拟子类不会继承注册的抽象基类,而且任何时候都不会检查它是否符合抽象基类的接口,即便在实例化时也不会检查。为了避免运行时错误,虚拟子类要实现所需的全部方法。

from random import randrange


@Tombola.register
class TomboList(list):

    def pick(self):
        if self:
            position = randrange(len(self))
            return self.pop(position)
        else:
            raise LookupError('pop from empty TomboList')

    load = list.extend

    def loaded(self):
        return bool(self)

    def inspect(self):
        return tuple(sorted(self))


Tombola.register(TomboList)