Python: '列表中的对象' 检查和 '__cmp__' 溢出

16 投票
5 回答
2216 浏览
提问于 2025-04-16 18:50

这是我第一次在Stack Overflow发帖,如果格式不太合适请见谅。我最近开始学习编程,差不多已经快两周了。我正在学习Python,网址是http://openbookproject.net/thinkcs/python/english3e/index.html,到目前为止一切都挺顺利,直到我卡在一个问题上,搞了几个小时也没找到解决办法,所以我来这里求助。

我想让OldMaidGame()按照第17章的说明正常运行,具体内容可以在这里找到:http://openbookproject.net/thinkcs/python/english3e/ch17.html。大部分代码也是来自前一章。

我发现我无法让Deck.remove、Hand.remove_matches或其他任何类型的remove函数正常工作。经过一些调试,我发现问题出在程序检查给定的牌是否在牌堆/手牌等地方时,它总是无法找到匹配的牌。然后我回头看了一下第16章,发现'if card in deck/hand/etc: remove(card)'等语句是通过查找对象的cmp()来判断牌是否存在于牌堆/手牌等地方的。这是我在给定代码基础上为'ace'做的cmp版本。

def __cmp__(self, other):
    """ Compares cards, returns 1 if greater, -1 if lesser, 0 if equal """
    # check the suits
    if self.suit > other.suit: return 1
    if self.suit < other.suit: return -1
    # suits are the same... check ranks
    # check for aces first.
    if self.rank == 1 and other.rank == 1: return 0
    if self.rank == 1 and other.rank != 1: return 1
    if self.rank != 1 and other.rank == 1: return -1
    # check for non-aces.
    if self.rank > other.rank: return 1
    if self.rank < other.rank: return -1
    # ranks are the same... it's a tie
    return 0

就我所知,这个cmp看起来没问题,当然我也希望能得到一些改进的建议(比如如何处理ace)。所以我不知道为什么在牌堆/手牌中的检查总是返回false。这是给定的remove函数:

class Deck:
    ...
    def remove(self, card):
        if card in self.cards:
            self.cards.remove(card)
            return True
        else:
            return False

我拼命想让它工作,结果想出了这个:

class Deck:
    ...
    def remove(self, card):
        """ Removes the card from the deck, returns true if successful """
        for lol in self.cards:
            if lol.__cmp__(card) == 0:
                self.cards.remove(lol)
                return True
        return False

看起来没问题,直到我继续处理其他不工作的remove函数:

class OldMaidHand(Hand):
    def remove_matches(self):
        count = 0
        original_cards = self.cards[:]
        for card in original_cards:
            match = Card(3 - card.suit, card.rank)
            if match in self.cards:
                self.cards.remove(card)
                self.cards.remove(match)
                print("Hand {0}: {1} matches {2}".format(self.name, card, match))
                count = count + 1
        return count

我又做了一些调整:

class OldMaidHand(Hand):
    def remove_matches(self):
        count = 0
        original_cards = self.cards[:]
        for card in original_cards:
            match = Card(3 - card.suit, card.rank)
            for lol in self.cards:
                if lol.__cmp__(match) == 0:
                    self.cards.remove(card)
                    self.cards.remove(match)
                    print("Hand {0}: {1} matches {2}".format(self.name, card, match))
                    count = count + 1
        return count

这个移除功能对牌有效,但当我尝试移除匹配时会出现错误(x不在列表中)。再过一个小时,我可能也能让它工作,但因为我觉得自己走错了路,无法解决最初的'牌在牌堆/手牌中'的问题,所以我来这里寻求一些答案或建议。

谢谢你们的阅读,我非常感激你们能提供的任何帮助 :)

--------------------- 编辑 1 *>

这是我当前的代码:http://pastebin.com/g77Y4Tjr

--------------------- 编辑 2 *>

我尝试了这里建议的每一个cmp,但仍然无法用'in'找到牌。

>>> a = Card(0, 5)
>>> b = Card(0, 1)
>>> c = Card(3, 1)
>>> hand = Hand('Baris')
>>> hand.add(a)
>>> hand.add(b)
>>> hand.add(c)
>>> d = Card(3, 1)
>>> print(hand)
Hand Baris contains
5 of Clubs
 Ace of Clubs
  Ace of Spades
>>> d in hand.cards
False
>>> 

我也尝试了DSM成功使用的card.py,但那里也出现了错误,比如在排序函数中,它说无法比较两个牌对象。
所以我在想,这可能是Python 3.2的问题,或者某个地方的语法发生了变化?

5 个回答

0

我似乎无法重现无法通过 Deck.remove 移除卡片的问题。如果我从 thinkpython 网站 的 card.py 开始,并添加你上面提到的 remove 函数,它似乎是可以正常工作的:

>>> deck = Deck()
>>> str(deck).split('\n')
['Ace of Clubs', '2 of Clubs', '3 of Clubs', '4 of Clubs', '5 of Clubs', '6 of Clubs', '7 of Clubs', '8 of Clubs', '9 of Clubs', '10 of Clubs', 'Jack of Clubs', 'Queen of Clubs', 'King of Clubs', 'Ace of Diamonds', '2 of Diamonds', '3 of Diamonds', '4 of Diamonds', '5 of Diamonds', '6 of Diamonds', '7 of Diamonds', '8 of Diamonds', '9 of Diamonds', '10 of Diamonds', 'Jack of Diamonds', 'Queen of Diamonds', 'King of Diamonds', 'Ace of Hearts', '2 of Hearts', '3 of Hearts', '4 of Hearts', '5 of Hearts', '6 of Hearts', '7 of Hearts', '8 of Hearts', '9 of Hearts', '10 of Hearts', 'Jack of Hearts', 'Queen of Hearts', 'King of Hearts', 'Ace of Spades', '2 of Spades', '3 of Spades', '4 of Spades', '5 of Spades', '6 of Spades', '7 of Spades', '8 of Spades', '9 of Spades', '10 of Spades', 'Jack of Spades', 'Queen of Spades', 'King of Spades']
>>> len(deck.cards)
52
>>> c = Card(suit=0, rank=8)
>>> str(c)
'8 of Clubs'
>>> c in deck.cards
True
>>> deck.remove(c)
True
>>> len(deck.cards)
51
>>> c in deck.cards
False
>>> str(deck).split('\n')
['Ace of Clubs', '2 of Clubs', '3 of Clubs', '4 of Clubs', '5 of Clubs', '6 of Clubs', '7 of Clubs', '9 of Clubs', '10 of Clubs', 'Jack of Clubs', 'Queen of Clubs', 'King of Clubs', 'Ace of Diamonds', '2 of Diamonds', '3 of Diamonds', '4 of Diamonds', '5 of Diamonds', '6 of Diamonds', '7 of Diamonds', '8 of Diamonds', '9 of Diamonds', '10 of Diamonds', 'Jack of Diamonds', 'Queen of Diamonds', 'King of Diamonds', 'Ace of Hearts', '2 of Hearts', '3 of Hearts', '4 of Hearts', '5 of Hearts', '6 of Hearts', '7 of Hearts', '8 of Hearts', '9 of Hearts', '10 of Hearts', 'Jack of Hearts', 'Queen of Hearts', 'King of Hearts', 'Ace of Spades', '2 of Spades', '3 of Spades', '4 of Spades', '5 of Spades', '6 of Spades', '7 of Spades', '8 of Spades', '9 of Spades', '10 of Spades', 'Jack of Spades', 'Queen of Spades', 'King of Spades']

如果我把 __cmp__ 替换成你提供的那个,也似乎没问题:

>>> deck = Deck()
>>> c = Card(suit=0,rank=1)
>>> c in deck.cards
True
>>> deck.remove(c)
True
>>> len(deck.cards)
51

所以肯定有什么地方不一样。你能把你的完整代码和一些能展示这个问题的代码放到某个地方吗?比如 pastebin、gist 等等?


(顺便说一下,我的 ace-over-king 比较函数看起来是这样的:

def __cmp__(self, other):
    def aceshigh(r): return 14 if r==1 else r
    t1 = self.suit, aceshigh(self.rank)
    t2 = other.suit, aceshigh(other.rank)
    return cmp(t1, t2)

练习:去掉魔法数字。)

1

我也无法重现这个错误。对我来说,一切都正常。我的唯一建议是,你可能不应该在遍历一个列表的时候去修改它(也就是说,在遍历self.cards的时候调用self.cards.remove)。这并不能解释为什么使用“in”的版本对你不起作用。

你的比较函数可以写得更简洁一些(在我看来也更简单),可以这样写:

def __cmp__(self, other):
    """ Compares cards, returns 1 if greater, -1 if lesser, 0 if equal """
    return cmp((self.suit,  self.rank == 1,  self.rank),
              (other.suit, other.rank == 1, other.rank))

或者如果你更喜欢这样:

    return (cmp(self.suit, other.suit) or
            cmp(self.rank == 1, other.rank == 1) or
            cmp(self.rank, other.rank))
6

“我在想,可能是Python 3.2有问题,或者语法在某个地方变了?”

哦,你在用Python 3.2吗? 这在Python 3中是行不通的:Python 3不使用__cmp__

你可以看看这个数据模型(找找__eq__。另外,看看这个Python 3的新特性,里面有很多你可能会错过的重要内容。

抱歉,这个问题是我们这些Python程序员的失误;我们本该更早发现这个问题。大多数人可能看了所有代码,没多想就意识到这些代码显然是Python 2的,然后假设我们在用的是这个版本。cmp函数在Python 3.2中根本不存在,但之所以没有报错,是因为__cmp__根本没有被调用。

如果我在Python 3.2中运行这段代码,我就会完全复现你的问题:

>>> c = Card(0,2)
>>> str(c)
'2 of Clubs'
>>> c in [c]
True
>>> c in Deck().cards
False

在Python 3中,你要么实现所有的丰富比较方法,要么实现__eq__和其中一个,然后使用一个总排序装饰器。

from functools import total_ordering

@total_ordering
class Card(object):
    """Represents a standard playing card."""
    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]
    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank
    def __str__(self):
        return '%s of %s' % (Card.rank_names[self.rank],
                             Card.suit_names[self.suit])
    def __repr__(self): return str(self)
    def __lt__(self, other):
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return t1 < t2
    def __eq__(self, other):
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return t1 == t2


>>> c = Card(2,3)
>>> c
3 of Hearts
>>> c in Deck().cards
True

撰写回答