Python: 'object in list' 检查和 '__cmp__' 溢出

16

这是我第一次在stackoverflow上,如果格式不太适合该网站,请见谅。我最近才开始学习编程,差不多已经过去了两个星期。我正在从http://openbookproject.net/thinkcs/python/english3e/index.html学习Python,一切都很好,直到现在,我卡了几个小时。

我谷歌过很多,但找不到一个合适的解决方案来解决我的问题,所以我来到这里。

我尝试让OldMaidGame()按照CH17的说明运行而没有问题。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”的版本。

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 checks)。 所以我不知道为什么牌组/手牌中的卡牌检查总是返回 false。 这是给定的移除函数:

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

看起来很正常,直到我转向其他不起作用的删除函数:

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,但在那里也出现了错误,例如在sort函数处,它说它无法比较两个卡对象。
所以我想知道,也许这是Python 3.2的问题,或者语法在某些地方发生了变化?


4
决定学习Python,加一! - AJ.
你确定这副牌里面真的有牌吗? - Björn Pollex
1
@Space_C0wb0y 是的,其他一切都正常。由于不确定这是否适合在此网站上发布整个程序,并且它可以在我链接的电子书中找到,因此没有发布整个程序。唯一失败的是'in'检查。即使我手动创建一个空牌组/手牌,添加1-2张卡片,并尝试检查程序是否识别其中任何一张牌时,它也会失败。 - Ulquiomaru
5个回答

6

"所以我在想,也许是Python 3.2的问题,或者语法在某个地方发生了变化?"

哦,你正在运行Python 3.2?这在Python 3中永远不会起作用:Python 3不使用__cmp__

请参阅数据模型(查找__eq__。此外,请阅读Python 3中的新内容,了解其他容易被忽略的事情。

抱歉,这是我们Python程序员的问题;我们应该更早地发现这个问题。大多数人可能看了所有代码,意识到源代码显然是Python 2代码,甚至没有考虑我们正在使用什么版本的Python。 cmp函数在Python 3.2中甚至不存在,但之所以不会出现NameError错误,是因为从未调用__cmp__

如果我在Python 3.2中运行代码,我会完全重现您的问题:

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

在Python 3中,你要么实现所有的rich cmps,要么实现__eq__和其中一个,然后使用total_ordering装饰器。
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

1

我也无法重现这个错误。对我来说它很好用。我的唯一建议是,在迭代列表时(即在self.cards上进行循环时调用self.cards.remove),您可能不应该修改列表。这不能解释为什么使用“in”关键字的版本对您无效。

您的cmp函数可以更简洁地编写(并且在我看来更简单),如下所示:

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))

0

听起来你的 deck 变量出了问题。要么是 remove 函数指向了一个空 deck 的不同对象,要么就是你有一个命名空间问题。remove 函数是 deck 对象的一部分吗?

我建议添加几个 print deck 行。一个在初始化后立即打印,以查看它里面有什么,另一个在调用 remove 时打印。


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


(顺便说一句,我的王牌超过国王的比较看起来像这样:)
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)

练习:移除魔法数字。


那个网站上的card.py对我也不起作用。而且我不是在说像打印语法这样的Python版本之间的小问题,甚至排序函数都会出错。太奇怪了。我只是一个两周大的Python用户,这真让我崩溃 :( - Ulquiomaru

0

你的比较函数应该是有效的,正如其他人所指出的那样,需要更多的细节来弄清楚发生了什么。至于建议:

  1. 将 Ace 放在 ranks 的末尾,使用 0-12 来映射 ranks。这对我来说似乎是自然的方法。

  2. 利用标准库:

    A. 使用 random.shuffle 进行洗牌。

    B. 使用 cmp 处理比较。

    C. collections.defaultdict 在我看来可以使 remove_matches 方法更加简洁。

  3. 建议中的 __str__ 方法真的非常烦人。

  4. 实现 __repr__

另一种实现方式:

from collections import defaultdict
import random

class Card(object):
    suits = ["Clubs", "Diamonds", "Hearts", "Spades"]
    ranks = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King", "Ace"]

    def __init__(self, suit=0, rank=0):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        return self.ranks[self.rank] + " of " + self.suits[self.suit]

    def __repr__(self):
        return self.__str__()

    def __cmp__(self, other):
        return cmp((self.suit, self.rank), (other.suit, other.rank))

class Deck(object):
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(13):
                self.cards.append(Card(suit=suit, rank=rank))

    def shuffle(self):
        random.shuffle(self.cards)

    def remove(self, card):
        if card in self.cards:
            self.cards.remove(card)

    def pop(self):
        return self.cards.pop()

    def is_empty(self):
        if len(self.cards) is 0:
            return True
        return False

    def deal(self, hands, num_cards=999):
        num_hands = len(hands)
        for i in range(num_cards):
            if self.is_empty(): break   # break if out of cards
            card = self.pop()           # take the top card
            hand = hands[i % num_hands] # whose turn is next?
            hand.add(card)              # add the card to the hand

class Hand(Deck):
    def __init__(self, name=""):
       self.cards = []
       self.name = name

    def add(self,card):
        self.cards.append(card)

class OldMaidHand(Hand):
    def remove_matches(self):
        matches = defaultdict(list)
        for card in self.cards:
            matches[card.rank].append(card)
        for cards in matches.values():
            if len(cards) == 2:
                print("Hand {0}: {1} matches {2}".format(self.name, *cards))
                for card in cards:
                    self.remove(card)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接