魔法方法#
1. 运算符相关#
1.1. 常规运算#
魔法方法 |
运算符 |
增量运算 |
反向运算 |
---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
||
|
1.2. 位运算#
魔法方法 |
运算符 |
增量运算 |
反向运算 |
---|---|---|---|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1.3. 比较与正负#
在标准库中有两例 x != +x
的情况。
decimal.Decimal
collections.Counter
魔法方法 |
运算符 |
魔法方法 |
运算符 |
---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2. 转换#
2.1. 字符串转换#
魔法方法 |
类方法 |
---|---|
|
|
|
|
|
|
|
|
2.2. 数值转换#
魔法方法 |
类方法 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3. 容器相关#
Python 的序列协议只需要 __len__
和 __getitem__
两个方法。任何类只要使用标准的签名和语义实现了这两个方法,就能用在任何期待序列的地方。
需要定制 list、dict 或 str 类型时,子类化 UserList
、UserDict
或 UserString
更简单。若所需的行为与内置类型区别很大,更容易的做法是,子类化 collections.abc
模块中相应的抽象基类。
3.1. 构建#
魔法方法 |
类方法 |
---|---|
|
|
|
|
|
|
|
|
|
|
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. 元素和变量#
魔法方法 |
类方法 |
---|---|
|
|
|
对含有 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. 实例#
魔法方法 |
类方法 |
---|---|
|
|
|
|
|
4.2. 属性#
魔法方法 |
调用场景 |
|
---|---|---|
|
访问一个缺省属性时 |
|
|
|
|
|
|
|
|
访问任何属性时 |
|
|
5. 函数相关#
魔法方法 |
类型 |
---|---|
|
dict |
|
|
|
method-wrapper |
|
tuple |
|
code |
|
tuple |
|
dict |
|
dict |
|
str |
|
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)