获取元素 -- __getitem__
__getitem__(self, key)
对于容器来说,获取元素是最重要的操作,魔术方法 __getitem__就完成了这个工作,每当对对象通过[]操作符获取元素时,解释器都会自动调用该魔术方法
import collections
Card = collections.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 __getitem__(self, position):
return self._cards[position]
索引与切片
我们建立了一个纸牌类,有了 __getitem__ 方法,我们就定义了索引操作,所有 dict 通过 [] 可以做的事,我们的纸牌类都可以做到:
>>> deck = FrenchDeck()
>>> deck[0]
Card(rank='2', suit='spades')
>>> deck[-1]
Card(rank='A', suit='hearts')
>>> deck[:3]
[Card(rank='2', suit='spades'), Card(rank='3', suit='spades'),
Card(rank='4', suit='spades')]
>>> deck[12::13]
[Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'),
Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')]
迭代
同时,我们的类也因此变得可以被迭代:
>>> for card in deck:
... print(card)
Card(rank='2', suit='spades')
Card(rank='3', suit='spades')
Card(rank='4', suit='spades')
或者反向迭代:
>>> for card in reversed(deck):
... print(card)
Card(rank='A', suit='hearts')
Card(rank='K', suit='hearts')
Card(rank='Q', suit='hearts')
in 操作
in 操作也同样可以:
>>> Card('Q', 'hearts') in deck
True
>>> Card('7', 'beasts') in deck
False
获取容量 -- __len__
__len__(self)
对于容器类,一个很重要的操作是获取容器中元素的数量 -- len()
我们曾经介绍过 Python 对象的内存结构:
len() 方法被调用时,Python 会自动调用对象的 __len__ 方法
对于内部类型,比如 list、dict、str、bytearray 等,__len__ 方法直接返回 PyVarObject 中的 ob_size 字段,而对于自定义类对象,你就需要去实现 __len__ 方法了
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
元素的更改与删除 -- __setitem__ 与 __delitem__
__setitem__(self, key, value)
__delitem__(self, key)
上面我们实现的容器类是不可变的,如果你想要改变或删除其中的元素就会报错:TypeError: 'FrenchDeck' object does not support item assignment
__setiem__ 与 __delitem__ 就是分别在更改容器元素值和删除元素时被自动调用的魔术方法
import collections
Card = collections.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 __getitem__(self, position):
return self._cards[position]
def __setitem__(self, key, value):
self._cards[key] = value
def __delitem__(self, key):
del self._cards[key]
if __name__ == '__main__':
deck = FrenchDeck()
print(deck[0])
deck[0] = Card('4', 'spades')
print(deck[0])
执行展示:Card(rank='2', suit='spades')
Card(rank='4', suit='spades')
容器的迭代 -- __iter__ 与 __reversed__
__iter__(self)
__reversed__(self)
定义 __getitem__ 以后,对象已经可以被循环迭代,但更好的方式是通过 __iter__ 方法返回迭代器来支持迭代
for x in containers 等方式的循环中,解释器会自动调用 __iter__ 方法获取迭代器进行迭代
而有时我们需要调用 python 的内建方法 reversed 来实现反向迭代,解释器就会自动调用 __reversed__ 方法
虽然上文提到,通过 __getitem__ 方法就可以实现上述功能,但迭代器会让这一过程的效率更高
容器元素的包含 -- __contains__ 与 __missing__
__contains__
__contains__(self, item)
当判断元素 in 或者 not in 容器时,python 解释器会自动调用 __contains__ 方法
__missing__
__missing__(self, key)
如果你的类是一个继承自 dict 的字典类,并且你没有实现自己的 __getitem__ 方法,那么当默认的 __getitem__ 方法发现 key 不存在时,就会调用你的 __missing__ 方法了
但是,需要注意的是,如果你自己实现了 __getitem__ 方法,并且没有调用父类的 __getitem__ 方法,那 __missing__ 将永远都不会被调用
这有两种方法可以解决:显式调用
class DictSubclass(dict):
def __getitem__(self, key):
if key not in self.data:
return self.__missing__(key)
return self.data[key]
使用父类 __getitem__
class DictSubclass(dict):
def __getitem__(self, key):
if key not in self.data:
return super(DictSubclass , self).__getitem__(key)
return self.data[key]