“Class”与“namedtuple”在 Python 中模拟一副牌
“Class” versus “namedtuple” to simulate a deck in Python
几本书(或教程)按以下方式定义一张牌和一副牌:
import random
class Card(object):
""" A card object with a suit and rank."""
RANKS = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)
SUITS = ('Spades', 'Diamonds', 'Hearts', 'Clubs')
def __init__(self, rank, suit):
"""Creates a card with the given rank and suit."""
self.rank = rank
self.suit = suit
def __str__(self):
"""Returns the string representation of a card."""
if self.rank == 1:
rank = 'Ace'
elif self.rank == 11:
rank = 'Jack'
elif self.rank == 12:
rank = 'Queen'
elif self.rank == 13:
rank = 'King'
else:
rank = self.rank
return str(rank) + ' of ' + self.suit
import random
class Deck(object):
""" A deck containing 52 cards."""
def __init__(self):
"""Creates a full deck of cards."""
self._cards = []
for suit in Card.SUITS:
for rank in Card.RANKS:
c = Card(rank, suit)
self._cards.append(c)
def shuffle(self):
"""Shuffles the cards."""
random.shuffle(self._cards)
def deal(self):
"""Removes and returns the top card or None
if the deck is empty."""
if len(self) == 0:
return None
else:
return self._cards.pop(0)
def __len__(self):
"""Returns the number of cards left in the deck."""
return len(self._cards)
def __str__(self):
"""Returns the string representation of a deck."""
result = ''
for c in self._cards:
result = self.result + str(c) + '\n'
return result
我最近读的一本书将其定义为:
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 __len__(self):
return len(self._cards)
def __getitem__(self, position):
return self._cards[position]
如果没有别的,这个版本“似乎”不那么冗长。 (不过这个问题不是我关心的,照原样比较代码长度是错误的。)
对于这个例子,也许在一般情况下,将卡片定义为 namedtuple 与 class 相比有哪些优点和缺点?
如果答案只是一个是可变的而另一个不是,我有什么理由关心这个?
一个版本比另一个更 Pythonic 吗?
命名元组实际上只是不那么冗长,因为您不需要 class 具有的样板 __init__
方法。
好的,所以你展示的实现也没有冗长的 __str__
函数,但是它作为字符串的表示又没有 class 版本所需的功能,所以比较代码量是不合理的
两者之间的重要区别在于 namedtuple
为您提供不可变对象,而上面显示的 class 是可变的(并且需要额外的代码才能使其不可变)。
额外的功能(如 khelwood 在评论中提到的)例如可以通过结合两者来处理:
class Card(collections.namedtuple('CardBase', ['rank', 'suit'])):
def __str__(self):
# code to say "Ace of spades" goes here
结果仍然具有只读 .rank
和 .suit
属性,尽管它现在有自己的其他可变属性字典,因此它不再是真正的不可变类型。如果您打算将只读属性与读写属性混合使用,那么使用 @property
可能比使用 namedtuple
更好,但如果您只是想要在其他方面非常适合 namedtuple
的东西上添加一些方便的功能,那么这个就可以了。
使用 namedtuple
最后一个可能的缺点是结果是一个元组。也就是说,可以使用 [0]
和 [1]
来访问它,可以使用 +
将卡片对象添加在一起,但结果毫无意义,其他所有元组都可以。对您的对象进行 nonsense/irrelevant 操作通常不会产生积极的危害,但它也不好,因为它会使您自动生成的文档膨胀,使错误更难发现,以及其他类似的烦恼。更改包含大量废话的已发布界面也更难,因为一旦您发布它,就可能有人会使用它。
几本书(或教程)按以下方式定义一张牌和一副牌:
import random
class Card(object):
""" A card object with a suit and rank."""
RANKS = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)
SUITS = ('Spades', 'Diamonds', 'Hearts', 'Clubs')
def __init__(self, rank, suit):
"""Creates a card with the given rank and suit."""
self.rank = rank
self.suit = suit
def __str__(self):
"""Returns the string representation of a card."""
if self.rank == 1:
rank = 'Ace'
elif self.rank == 11:
rank = 'Jack'
elif self.rank == 12:
rank = 'Queen'
elif self.rank == 13:
rank = 'King'
else:
rank = self.rank
return str(rank) + ' of ' + self.suit
import random
class Deck(object):
""" A deck containing 52 cards."""
def __init__(self):
"""Creates a full deck of cards."""
self._cards = []
for suit in Card.SUITS:
for rank in Card.RANKS:
c = Card(rank, suit)
self._cards.append(c)
def shuffle(self):
"""Shuffles the cards."""
random.shuffle(self._cards)
def deal(self):
"""Removes and returns the top card or None
if the deck is empty."""
if len(self) == 0:
return None
else:
return self._cards.pop(0)
def __len__(self):
"""Returns the number of cards left in the deck."""
return len(self._cards)
def __str__(self):
"""Returns the string representation of a deck."""
result = ''
for c in self._cards:
result = self.result + str(c) + '\n'
return result
我最近读的一本书将其定义为:
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 __len__(self):
return len(self._cards)
def __getitem__(self, position):
return self._cards[position]
如果没有别的,这个版本“似乎”不那么冗长。 (不过这个问题不是我关心的,照原样比较代码长度是错误的。)
对于这个例子,也许在一般情况下,将卡片定义为 namedtuple 与 class 相比有哪些优点和缺点?
如果答案只是一个是可变的而另一个不是,我有什么理由关心这个?
一个版本比另一个更 Pythonic 吗?
命名元组实际上只是不那么冗长,因为您不需要 class 具有的样板 __init__
方法。
好的,所以你展示的实现也没有冗长的 __str__
函数,但是它作为字符串的表示又没有 class 版本所需的功能,所以比较代码量是不合理的
两者之间的重要区别在于 namedtuple
为您提供不可变对象,而上面显示的 class 是可变的(并且需要额外的代码才能使其不可变)。
额外的功能(如 khelwood 在评论中提到的)例如可以通过结合两者来处理:
class Card(collections.namedtuple('CardBase', ['rank', 'suit'])):
def __str__(self):
# code to say "Ace of spades" goes here
结果仍然具有只读 .rank
和 .suit
属性,尽管它现在有自己的其他可变属性字典,因此它不再是真正的不可变类型。如果您打算将只读属性与读写属性混合使用,那么使用 @property
可能比使用 namedtuple
更好,但如果您只是想要在其他方面非常适合 namedtuple
的东西上添加一些方便的功能,那么这个就可以了。
使用 namedtuple
最后一个可能的缺点是结果是一个元组。也就是说,可以使用 [0]
和 [1]
来访问它,可以使用 +
将卡片对象添加在一起,但结果毫无意义,其他所有元组都可以。对您的对象进行 nonsense/irrelevant 操作通常不会产生积极的危害,但它也不好,因为它会使您自动生成的文档膨胀,使错误更难发现,以及其他类似的烦恼。更改包含大量废话的已发布界面也更难,因为一旦您发布它,就可能有人会使用它。