A subtle point: Semantics
If it looks too easy so far, that's because it isn't quite this easy.
What we have at the moment looks like it works only because we have not
investigated what it has done closely enough. Let's add a few more
print statements and rerun that last program.
Code:
print('Before:')
roxx = Hand()
roxx = roxx + Card(42) + Card(13) + Card(2)
print('roxx:', roxx)
chris = Hand()
chris = chris + Card(3) + Card(4)
print('chris:', chris)
print('After:')
new = roxx + chris
print('new:', new)
print('roxx:', roxx)
print('chris:', chris)
Output:
>>>
Before:
roxx: 4 of Spades, A of Diamonds, 3 of Clubs
chris: 4 of Clubs, 5 of Clubs
After:
new: 4 of Spades, A of Diamonds, 3 of Clubs, 4 of Clubs, 5 of Clubs
roxx: 4 of Spades, A of Diamonds, 3 of Clubs, 4 of Clubs, 5 of Clubs
chris: 4 of Clubs, 5 of Clubs
>>>
Uh oh! The value of roxx is changed by what we've done in
creating new which may or may not be the semantics we intended by
the statement new = roxx + chris. The problem here is that we are
building our return value from an existing object so we are changing the
existing object as we do so, and then ending up with a shared reference
to a single modified object. And we know from our first encounter with
shared references that they can be a serious problem. The fix is to
create a new CardCollection object in __add__, fill it with the
necessary values, and then return it. The result is code like this,
def __add__(self, other):
# Create a new CardCollection.
new_cc = CardCollection()
# Put a copy of self's cards into it.
new_cc.cards = self.cards[:]
if isinstance(other, Card):
new_cc.cards.append(other)
return new_cc
elif isinstance(other, CardCollection):
new_cc.cards.extend(other.cards[:])
return new_cc
else:
print('You can only add Cards to CardCollections!')
which produces the output,
Before:
roxx: 4 of Spades, A of Diamonds, 3 of Clubs
chris: 4 of Clubs, 5 of Clubs
After:
new: 4 of Spades, A of Diamonds, 3 of Clubs, 4 of Clubs, 5 of Clubs
roxx: 4 of Spades, A of Diamonds, 3 of Clubs
chris: 4 of Clubs, 5 of Clubs
>>>
There are further semantic issues. When we write new = roxx + chris,
to create a new Hand from roxx and chris,
should roxx and chris be emptied? That is, are we taking the cards
out of the hands roxx and chris to create new like a game of Go
Fish? Or does new = roxx + chris mean to make a hand called new that
is like the combination of new = roxx + chris? Sorting out
the meaning of operations is semantics, and the semantics of
collection types is notoriously tricky. There aren't universally right
and wrong answers to these questions. What we strive for are answers
that fit the problem domain, i.e. code that matches our experience of
the real-world situation we are modelling.