我定义了一个包含对象列表集合的类,并定义了__iter__
和__next__
方法以使其成为 loopable 。这里的集合是一个Deck
类,它包含Card
个对象的列表。
import random
class Card:
@staticmethod
def get_ranks():
return ("A", "K", "Q", "J", "10", "9", "8", "7", "6", "5", "4", "3", "2") # A is highest, 2 is lowest
@staticmethod
def get_suites():
return ("H", "D", "S", "C")
def __init__(self, suite, rank):
if suite not in Card.get_suites():
raise Exception("Invalid suite")
if rank not in Card.get_ranks():
raise Exception("Invalid rank")
self.suite = suite
self.rank = rank
def __lt__(self, card2):
self_rank = Card.get_ranks().index(self.rank)
card2_rank = Card.get_ranks().index(card2.rank)
return self_rank > card2_rank
def __le__(self, card2):
self_rank = Card.get_ranks().index(self.rank)
card2_rank = Card.get_ranks().index(card2.rank)
return self_rank >= card2_rank
def __gt__(self, card2):
self_rank = Card.get_ranks().index(self.rank)
card2_rank = Card.get_ranks().index(card2.rank)
return self_rank < card2_rank
def __ge__(self, card2):
self_rank = Card.get_ranks().index(self.rank)
card2_rank = Card.get_ranks().index(card2.rank)
return self_rank <= card2_rank
def __eq__(self, card2):
self_rank = Card.get_ranks().index(self.rank)
card2_rank = Card.get_ranks().index(card2.rank)
return self_rank == card2_rank
def __ne__(self, card2):
self_rank = Card.get_ranks().index(self.rank)
card2_rank = Card.get_ranks().index(card2.rank)
return self_rank != card2_rank
def __str__(self):
return(self.rank + self.suite)
def __repr__(self):
return str(self)
class Deck:
def __init__(self):
self.contents = [Card(suite, rank) for suite in Card.get_suites() for rank in Card.get_ranks()]
random.shuffle(self.contents)
self.index = 0
def __len__(self):
return len(self.contents)
def __iter__(self):
return self
def __next__(self):
if self.index == len(self.contents):
raise StopIteration
item = self.contents[self.index]
self.index += 1
return item
def pick_card(self):
choice = random.randrange(len(self))
card = self.contents.pop(choice)
return card
def return_card_and_shuffle(self, card):
self.contents.append(card)
random.shuffle(self.contents)
def __str__(self):
dstr = ''
for card in self:
dstr += str(card) + ", "
return "{} cards: ".format(len(self)) + dstr[:-2]
def deal_bookends(deck):
card1 = deck.pick_card()
card2 = deck.pick_card()
if card1 > card2:
temp = card1
card1 = card2
card2 = temp
return (card1, card2)
if __name__ == '__main__':
deck = Deck()
for _ in range(3):
c1, c2 = deal_bookends(deck)
print("We have {} and {}".format(c1, c2))
print(deck)
deck.return_card_and_shuffle(c1)
print(deck)
print(deck.contents[-4:])
deck.return_card_and_shuffle(c2)
print(deck)
print(deck.contents[-4:])
We have 8H and KH
50 cards: 9H, 8C, AC, 7C, 6H, 2S, 2D, 5C, 10H, 5H, JS, 5S, KD, JH, JC, QS, 2H, 3H, 3S, 3D, 4C, 4H, AD, KS, JD, QH, 10D, 6S, 5D, 8D, 3C, 6C, 7D, AS, 7H, AH, 9S, 10C, QC, QD, 7S, 2C, KC, 8S, 4D, 4S, 6D, 10S, 9D, 9C
51 cards: QS
[7D, 5C, 10H, QS]
52 cards: 10C
[KC, 3S, 9H, 10C]
We have 2C and QD
Traceback (most recent call last):
File "playing_cards.py", line 106, in <module>
print(deck)
File "playing_cards.py", line 88, in __str__
for card in self:
File "playing_cards.py", line 73, in __next__
item = self.contents[self.index]
IndexError: list index out of range
当我将卡片对象推回到列表中时,似乎事情不适用于for循环的第二次运行。如何在保持弹出,推送功能的同时解决这个问题。
修改:self.index
在第一次调用print()
后为50。当卡被添加回列表时,索引保持在50,而甲板长度现在是51卡。因此,在第二次(和第三次)打印调用打印最后一张卡而不是整个卡。然后出现错误。
我想我在这里读过documentation错了。我的问题是我应该在StopIteration
位重置索引。这是正确的方法吗,或者索引是否应该自行重置?
答案 0 :(得分:2)
注意:如果您试图通过实现自己的迭代器来学习迭代器的工作方式,那么上述建议就成立了。如果您只想让Deck
迭代,您可以在Deck
中执行此操作:
def __iter__(self):
return self.contents # lists are already iterable
更好的是,如果你希望你的套牌表现得像一个列表(迭代,索引,切片,删除),你可以扩展list
。
了解迭代器的工作原理:
你遇到的问题是你正在将一个集合与一个迭代器混为一谈。集合应该包含一组项目。您的Deck
是一个集合。一个集合是可迭代的,这意味着我可以对它进行for x in collection
。当我们执行for x in collection
时,Python实际上会for x in iter(collection)
,它将集合转换为迭代器。
您希望迭代器和集合是分开的。如果你的集合是它自己的迭代器,那么你一次只能有一个迭代器(它本身)。另请注意,迭代器应该只使用一次。通过在self.index = 0
中执行__iter__
,您可以使迭代器(Deck
)重复使用。
请考虑以下事项:
nums = [1, 2, 3]
for i in nums:
for j in nums:
print(i, j)
我们希望这会回来:
1 1
1 2
1 3
2 1
2 2
2 3
3 1
3 2
3 3
请注意,每次内循环遍历整个集合。如果nums
是它自己的迭代器,那么我们就会遇到一些问题:
# Here internally nums sets the current index as 0
for i in nums:
# Here internally nums sets the current index as 0 again
for j in nums:
print(i, j)
# Once this inner loop finishes the current index is 4.
# But that is also the index for the outer loop, so the
# outer loop ends too
意外输出:
1 1
1 2
1 3
解决方案是Deck.__iter__
应该返回一个名为DeckIterator
的新对象,该对象会跟踪自己的index
。 DeckIterator.__iter__
应该返回self(如required by the docs),但这只是一个细节。通过这样做,您可以立即在甲板上启用多次迭代,按预期工作。
所以最小的例子是:
class Deck:
# ... snip ...
def __iter__(self):
return DeckIterator(self.contents)
class DeckIterator:
def __init__(self, cards):
self.index = 0
self.cards = cards
def __iter__(self):
return self
def __next__(self):
if self.index >= len(self.cards):
# We've gotten the end of the deck/list
raise StopIteration
item = self.cards[self.index]
self.index += 1
return item
另外,如果你不相信我这个列表作为它自己的迭代器,这里有一个表现出这种不良行为的列表:
class BadList(list):
def __iter__(self):
self._current_index = 0
return self
def __next__(self):
print(f'current index is {self._current_index}', end='')
if self._current_index >= len(self):
print(' which is the end, so ending iteration')
raise StopIteration
item = self[self._current_index]
print(f' so returning value {item}')
self._current_index += 1
return item
# Using letters instead of numbers so difference between indices
# and items is more clear
letters = BadList('abc')
for i in letters:
for j in letters:
print(i, j)
从中输出:
current index is 0 so returning value "a"
current index is 0 so returning value "a"
a a
current index is 1 so returning value "b"
a b
current index is 2 so returning value "c"
a c
current index is 3 which is the end, so ending iteration
current index is 3 which is the end, so ending iteration
答案 1 :(得分:0)
不确定你是怎么到达那里的,但是你已经超出了你的名单。建议你比较列表的>=
长度,如:
def __next__(self):
if self.index >= len(self.contents):
raise StopIteration
.....
答案 2 :(得分:0)
进行以下更改
def __iter__(self):
self.index = 0
return self
因此,每次调用__iter__
时,都会重置index
。
在迭代结束时,deck
迭代self.index == len(self.contents)
后,您收到此错误的原因是{。{1}}。
下次重复时,self.index
应重置为0
。
我做了上述修改,对我有用。
答案 3 :(得分:0)
目前您的具体问题是由于您的__next__
方法检查不够通用,无法检测到您已经迭代过self.contents
中最后一个值的所有情况。由于self.contents
可以更改,因此您需要使用大于或等于的测试:
if self.index >= len(self.contents):
这将解决当前问题,但您仍会遇到其他问题,因为您的Deck
只能迭代一次。这是因为您已经实现了迭代器协议,而不是可迭代协议。这些很容易混淆,所以如果你不立即明白这些差异,不要感到难过。
iterable是具有返回迭代器的__iter__
方法的任何对象。有些iterables每次调用它们都会返回不同的迭代器,因此您可以多次迭代它们。
迭代器实现__next__
方法,该方法产生下一个值或引发StopIteration
。迭代器还必须有一个__iter__
方法,它返回自身,允许在需要迭代的地方使用迭代器,尽管它只能迭代一次。
对于Deck
,实现可迭代协议可能是有意义的,并且每次调用__iter__
时都返回一个单独的迭代器。它实现自己的迭代器类型很少有用,但如果你想测试你对不同协议如何组合在一起的知识,那就很有趣了:
class Deck:
def __init__(self):
self.contents = [Card(suite, rank)
for suite in Card.get_suites()
for rank in Card.get_ranks()]
random.shuffle(self.contents)
# no index here
def __iter__(self):
return DeckIterator(self)
# other methods, but no __next__
class DeckIterator:
def __init__(self, deck):
self.deck = deck
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index > len(self.deck):
raise StopIteration
value = self.deck.contents[self.index]
self.index += 1
return value
更实用的方法是让Deck.__iter__
借用一些方便的迭代器类型。例如,您可以执行return iter(self.contents)
并获得一个与上面的自定义版本完全相同的迭代器。另一个选择是使__iter__
成为生成器函数,因为生成器对象是迭代器。如果您在迭代它时需要对每个项目进行一些处理,这将非常方便。