简介
魔术方法(magic method)是特殊方法的昵称,在Python中的特殊方法,一般都是使用诸如__xxx__
(前后两个下划线,中间是方法名)的命名方式,在书里有个名词也可以形容它,比如__getitem__
,叫做“双下—getitem” (dunnder-getitem)
万事开头难
难以用概念去概括Python特殊方法的作用,最简单的方法就是用例子说明。
很多人都会选择使用Python作为快速开发工具,而特殊方法是属于“快速”这个性质。
在Python中,要拿到一个集合的某个元素,可以使用对应的引索进行取值,比如list[key]
,这背后利用的是__getitem__
方法,为了拿到my_list[key]
的值,解释器实际上会调用my_list.__getitem__(key)
。
Python也是面向对象编程语言,对于求一个集合的长度使用len(list)
而不是list.len()
会感觉有点奇怪,背后就是特殊方法的作用,调用了list.__len__()
方法,和面向对象完全符合,而且还起到简化的作用,变得更加通俗易懂。
一个完整的例子
代码
import collections
Card = collections.namedtuple('Card',['rank', 'suit']) # 具名元组
class FrenchDeck:
ranks = [str(n) for n in range(2,11)] + list('JQKA') # 牌数
suits = 'spades hearts clubs diamonds'.split() # 牌色
def __init__(self): # 初始化(构造函数)
self._cards = [Card(rank, suit) for suit in self.suits
for rank in self.ranks]
def __len__(self): # 用len取长度的特殊方法
return len(self._cards)
def __getitem__(self, position): # 用引索取值的特殊方法
return self._cards[position]
运行
>>> deck = FrenchDeck()
>>> len(deck)
52
>>> deck[0]
Card(rank='2', suit='spades')
随机抽牌
那么我们可以使用random模块里面的一个方法随机抽取一张牌
>>> from random import choice
>>> choice(deck)
Card(rank='2', suit='clubs')
>>> choice(deck)
Card(rank='J', suit='clubs')
切片操作
既然我们实现了引索中括号[]操作,那么我们也可以使用切片操作:
>>> 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='hearts'),
Card(rank='A', suit='clubs'), Card(rank='A', suit='diamonds')]
迭代操作
另外,仅仅实现了__getitem__
方法,那么也变成了可迭代的了:
>>> for card in deck: # 反向迭代也可以 reversed(deck)
print(card)
Card(rank='2', suit='spades')
Card(rank='3', suit='spades')
Card(rank='4', suit='spades')
···
Card(rank='Q', suit='diamonds')
Card(rank='K', suit='diamonds')
Card(rank='A', suit='diamonds')
in运算符
如果一个集合没有实现__contains__
方法,那么in
运算符会按顺序做一次迭代搜索。
>>> Card('A', 'hearts') in deck
True
>>> Card('B', 'hearts') in deck
False
排序
按照扑克牌的大小,2最小,A最大,同时要加上花色的大小判定,从大到小排序:黑桃、红桃、梅花、方块。
suit_values = dict(spades = 3, hearts = 2, clubs = 1, diamonds = 0)
def spades_high(card):
rank_value = FrenchDeck.ranks.index (card.rank)
return rank_value * len(suit_values) + suit_values[card.suit]
运行
>>> for card in sorted(deck, key = spades_high):
print(card)
Card(rank='2', suit='diamonds')
Card(rank='2', suit='clubs')
Card(rank='2', suit='hearts')
Card(rank='2', suit='spades')
···
Card(rank='A', suit='diamonds')
Card(rank='A', suit='clubs')
Card(rank='A', suit='hearts')
Card(rank='A', suit='spades')