在 Python 中模拟牌组的“Class”与“namedtuple”

bla*_*ned 2 python collections class namedtuple python-3.x

几本书(或教程)按以下方式定义了一张卡片和一副牌:

\n\n
import random\n\nclass Card(object):\n    """ A card object with a suit and rank."""\n\n    RANKS = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)\n\n    SUITS = (\'Spades\', \'Diamonds\', \'Hearts\', \'Clubs\')\n\n    def __init__(self, rank, suit):\n        """Creates a card with the given rank and suit."""\n        self.rank = rank\n        self.suit = suit\n\n    def __str__(self):\n        """Returns the string representation of a card."""\n        if self.rank == 1:\n            rank = \'Ace\'\n        elif self.rank == 11:\n            rank = \'Jack\'\n        elif self.rank == 12:\n            rank = \'Queen\'\n        elif self.rank == 13:\n            rank = \'King\'\n        else:\n            rank = self.rank\n        return str(rank) + \' of \' + self.suit\n\nimport random\n\nclass Deck(object):\n    """ A deck containing 52 cards."""\n\n    def __init__(self):\n        """Creates a full deck of cards."""\n        self._cards = []\n        for suit in Card.SUITS:\n            for rank in Card.RANKS:\n                c = Card(rank, suit)\n                self._cards.append(c)\n\n    def shuffle(self):\n        """Shuffles the cards."""\n        random.shuffle(self._cards)\n\n    def deal(self):\n        """Removes and returns the top card or None \n        if the deck is empty."""\n        if len(self) == 0:\n           return None\n        else:\n           return self._cards.pop(0)\n\n    def __len__(self):\n       """Returns the number of cards left in the deck."""\n       return len(self._cards)\n\n    def __str__(self): \n        """Returns the string representation of a deck."""\n        result = \'\'\n        for c in self._cards:\n            result = self.result + str(c) + \'\\n\'\n        return result\n
Run Code Online (Sandbox Code Playgroud)\n\n

我最近读的一本书将其定义为:

\n\n
import collections\n\nCard = collections.namedtuple(\'Card\', [\'rank\', \'suit\'])\n\nclass FrenchDeck:\n    ranks = [str(n) for n in range(2, 11)] + list(\'JQKA\')\n    suits = \'spades diamonds clubs hearts\'.split()\n\n    def __init__(self):\n        self._cards = [Card(rank, suit) for suit in self.suits\n                                        for rank in self.ranks]\n\n    def __len__(self):\n        return len(self._cards)\n\n    def __getitem__(self, position):\n        return self._cards[position]\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果不出意外的话,这个版本 \xe2\x80\x9c 似乎 \xe2\x80\x9d 不太冗长。(但这不是我关心的问题。事实上,比较代码的长度是错误的。)

\n\n

对于这个例子,也许一般而言,将卡定义为namedtuple与class相比有何优缺点?

\n\n

如果答案只是一个是可变的而另一个则不是,我有什么理由关心这一点?

\n\n

一个版本比另一个版本更Pythonic吗?

\n

Ste*_*sop 5

命名元组实际上只是不太冗长,因为您不需要__init__该类具有的样板方法。

好的,所以您显示的实现也没有冗长的__str__函数,但是它作为字符串的表示形式又不具有类版本所需的功能,因此比较代码量是不合理的。

两者之间的重要区别在于,它为namedtuple您提供了不可变的对象,而上面显示的类是可变的(并且需要额外的代码才能使其不可变)。

额外的功能(如 khelwood 在评论中提到的)可以通过结合两者来处理:

class Card(collections.namedtuple('CardBase', ['rank', 'suit'])):
    def __str__(self):
        # code to say "Ace of spades" goes here
Run Code Online (Sandbox Code Playgroud)

结果仍然具有只读.rank.suit属性,尽管它现在有自己的其他可变属性的字典,因此它不再是真正的不可变类型。如果您打算将只读属性与读写属性混合在一起,那么使用 可能会比@property使用更好namedtuple,但如果您只是想将一些方便的功能放在本来很适合 的东西上namedtuple,那么这是可行的。

使用的最后一个可能的缺点namedtuple是结果是一个元组。也就是说,它可以使用[0]and进行访问[1],卡片对象可以使用无意义的结果相加在一起+,以及元组的其他所有操作。对对象进行无意义/不相关的操作通常不会造成任何危害,但也不好,因为它会使自动生成的文档变得臃肿,使错误更难发现,以及其他类似的烦恼。更改包含大量废话的已发布界面也更困难,因为一旦你发布它,有人可能会使用它。

  • 除了失去 \_\_slots\_\_ 之外,您还失去了继承元组的所有功能。用``等级,花色=卡片``拆包;哈希能力:“somedict[card]=value”,以“card1 < card”和“sorted(cards)”排序。 (3认同)