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.