【问题标题】:“Class” versus “namedtuple” to simulate a deck in Python“Class”与“namedtuple”在 Python 中模拟套牌
【发布时间】:2015-11-20 13:12:03
【问题描述】:

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

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 吗?

【问题讨论】:

  • A namedtuple 具有不可变的实例,并且可能不包含任何用户定义的函数。一个类可能不是不可变的(至少不是非常不可变),你可以在其中放入一堆有用的函数。

标签: python class python-3.x collections namedtuple


【解决方案1】:

命名元组确实不那么冗长,因为您不需要类具有的样板__init__ 方法。

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

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

额外的功能(如 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] 访问它,使用+ 可以将卡片对象相加在一起,结果毫无意义,其他元组都可以。对您的对象进行无意义/不相关的操作通常不会有害,但这也不好,因为它会使您的自动生成的文档膨胀,使错误更难找到,以及其他类似的烦恼。更改包含大量废话的已发布界面也更难,因为一旦您发布它,有人可能会使用它。

【讨论】:

  • 虽然你失去了namedtuple的一些优势,如果你不在类定义中说__slots__=()
  • @ppperry:说得好,对我来说,添加这一点比说出我所说的不再是不可变的要容易!
  • 除了丢失 __slots__ 之外,您还丢失了继承元组的所有功能。用rank, suit = card解包;哈希性:somedict[card]=value,使用card1 < cardsorted(cards) 排序。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-08
  • 2018-03-17
  • 1970-01-01
  • 2019-08-03
相关资源
最近更新 更多