Reputation: 3624
If I have the following defined:
Card = namedtuple('Card', ['rank', 'suit'])
class CardDeck():
ranks = [str(x) for x in range(2, 11)] + list('JQKA')
suits = 'spades diamonds clubs hearts'.split()
def __init__(self):
self._cards = [Card(rank, suit) for rank in self.ranks for suit in self.suits]
def __getitem__(self, index):
return self._cards[index]
How the in
operator is supported without having the __contains__
dunder method defined. For example the following:
deck = CardDeck()
print(Card('2', 'hearts') in deck)
will output:
True
Any Ideas?
Upvotes: 2
Views: 102
Reputation: 1122322
__getitem__
is used as a fallback when no __contains__
or __iter__
method is available. See the Membership test operations section of the expressions reference documentation:
Lastly, the old-style iteration protocol is tried: if a class defines
__getitem__()
,x in y
isTrue
if and only if there is a non-negative integer index i such thatx is y[i] or x == y[i]
, and no lower integer index raises theIndexError
exception.
So what actually happens is that Python simply uses an increasing index, which in Python would look something like this:
from itertools import count
def contains_via_getitem(container, value):
for i in count(): # increments indefinitely
try:
item = container[i]
if value is item or value == item:
return True
except IndexError:
return False
This treatment extends to all iteration functionality. Containers that do not implement __iter__
but do implement __getitem__
can still have an iterator created for them (with iter()
or the C-API equivalent):
>>> class Container:
... def __init__(self):
... self._items = ["foo", "bar", "baz"]
... def __getitem__(self, index):
... return self._items[index]
...
>>> c = Container()
>>> iter(c)
<iterator object at 0x1101596a0>
>>> list(iter(c))
['foo', 'bar', 'baz']
Containment tests via iteration are, of course, not really efficient. If there is a way to determine if something is an item in the container without a full scan, do implement a __contains__
method to provide that better implementation!
For a card deck, I can imagine that simply returning True
when the item is a Card
instance should suffice (provided the Card
class validates the rank and suit parameters):
def __contains__(self, item):
# a card deck contains all possible cards
return isinstance(item, Card)
Upvotes: 3
Reputation: 23753
From the documemtation
object.__contains__(self, item) ...
For objects that don’t define __contains__(), the membership test
first tries iteration via __iter__(), then the old sequence iteration
protocol via __getitem__(),...
see this section in the language reference.
Upvotes: 2