There should be one — and preferably only one — obvious way to do it.
Guido设计Python的宗旨之一就是每个功能都应该只对应唯一一个明确的操作。遵循这种设计规范,带来的好处就是各个库的一致性。比如说,当你尝试一个新的第三方数据结构时,想知道里面的元素有多少,大多数情况下你可以直接使用len(instance),而不是去猜测是不是count(),size()之类的。如果你设计自己的集合类,也能提供标准的len()来返回实例中成员的数量,那么你设计的类已经开始有Pythonic类的样子了。按照类的设计规范设计的类是Pythonic,这样的规范也被称为Python data model。Python data model describes the API that you can use to make your own objects play well with the most idiomatic language features.
为了方便行文,我们不妨称自己设计的集合类叫做StandardCollection,创建一个StandardCollection实例sc。要是我们能通过sc[0]来访问第一个元素,那么StandardCollection又往Pythonic迈进了一大步。所以,究竟该如何让自己的类的len()和索引等操作符合规范的预期呢?在Java中,可以通过interface来规范开发者的设计,一个实现了某个接口的类,也要实现这个接口提供的方法。接口的方法便是一种设计规范,约定每个行为对应的方法调用。在python中也有类似的东西,他们被称作magic method或者是dunder method,基本形式是__func__,即函数名前后都带上两个下划线。如果想len(sc)返回sc中的元素个数, 就在StandardCollection类的定义中定义方法__len__(self)。如果希望通过数字索引来访问元素,就在StandardCollection类中定义方法__getitem__(self, index)。
终于开始点题了,接下来我会展示一个Pythonic Card Deck类。他是一个扑克牌的集合类,里面含有52张牌(没有大小鬼)。为了让这个类变得Pythonic(一个集合类该有的操作都可以通过标准的操作来实现),我在方法中定义了__len__和__getitem__。
import collections
from random import choice
Card = collections.namedtuple('Card', ['rank', 'suit'])
class Deck:
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]
deck = Deck()
print(len(deck)) # 52
print(deck[1]) # Card(rank='3', suit='spades')
print(deck[0:2]) # [Card(rank='2', suit='spades'), Card(rank='3', suit='spades')]
print(deck[0::13])
# randomly choose a card
print(choice(deck))
# iterator
for card in deck:
print(card)
实现一个__getitem__方法可以给你带来很多好处:1)数字索引访问、2)随机访问、3)切片、4)迭代器。
注意__len__的使用条件,如果你定义了一个线段类Line,用len(line)来获取线段的长度是不对的。因为magic method __len__要求必须返回整数。所以,__len__ 主要是用在集合类上的,表示集合中元素的个数。
虽然我标记这篇文章是原创,实际是以上内容都来自于Luciano Ramalho编著的《Fluent Python》。有天,realpython.com发了封订阅邮件给我,讲一个人抱怨python如何如何地糟糕。然而笔者的第一反应就是,自己作为一个资深的python爱好者,不允许别人如此偏颇地评价python。这样的自称真地触动到我了。自己作为程序员,无论是学什么语言,都是半斤八两。如果连在自己从事的行业中都是业余的,自己的人生是不是也是业余的,只是一个灵魂装到壳子里到这世上走一圈。