jimmy Light
jimmy Light

Reputation: 107

Having trouble understanding order of evaluation in Python method call

Just wondering if anyone can help me understand this code a little better. Here is the program:

class Card(object):
    """ A playing card. """
    RANKS = ["A", "2", "3", "4", "5", "6", "7",
             "8", "9", "10", "J", "Q", "K"]
    SUITS = ["c", "d", "h", "s"]

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

    def __str__(self):
        rep = self.rank + self.suit
        return rep

class Hand(object):
""" A hand of playing cards. """
    def __init__(self):
        self.cards = []

    def __str__(self):
        if self.cards:
           rep = ""
           for card in self.cards:
               rep += str(card) + "\t"
        else:
            rep = "<empty>"
        return rep

    def clear(self):
        self.cards = []

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

    def give(self, card, other_hand):
        self.cards.remove(card)
            other_hand.add(card)


class Deck(Hand):
    """ A deck of playing cards. """

    def populate(self):
        for suit in Card.SUITS:
            for rank in Card.RANKS: 
                self.add(Card(rank, suit))   # <- HERE

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

    def deal(self, hands, per_hand = 1):
        for rounds in range(per_hand):
            for hand in hands:
                if self.cards:
                    top_card = self.cards[0]
                    self.give(top_card, hand)
                else:
                    print("Can't continue deal. Out of cards!")


# main
deck1 = Deck()
print("Created a new deck.")
print("Deck:")
print(deck1)

deck1.populate()
print("\nPopulated the deck.")
print("Deck:")
print(deck1)

The thing I'm wondering about is self.add(Card(rank, suit)) it's confusing me. Does Card(rank, suit) get sent to the add method straight away or does it get sent to the Card class first and then go to the add method? The add method has a cardparameter, what gets put into that parameter?

If I'm not making much sense it's because I've been staring at this for 2 hours.

Thanks

Upvotes: 2

Views: 160

Answers (4)

steveha
steveha

Reputation: 76725

It's pretty simple.

Any time you call a class in Python, you create a new instance of the class. So if you call the Card class, you create a new instance of class Card... in other words, you create a new card.

Let's create the ace of spades:

ace_of_spades = Card('A', 's')

But I'm cheating. I looked and saw what strings to pass to Card when I should be using the pre-defined constants. Let's do it more properly:

ace_of_spades = Card(Card.RANKS[0], Card.SUITS[3])

Hmm, I'm not sure that Card.RANKS[0] is that easy to understand. Personally I would probably do something like

class Card(object):
    RANK_ACE = 'A'
    RANK_2 = '2'
    # and so on

and then:

ace_of_spades = Card(Card.RANK_ACE, Card.SUIT_SPADES)

Now that we have used the variable name ace_of_spades to hold a reference to the card, we can use the card:

hand = Hand()
hand.add(ace_of_spades)

But if we just want to get the card into the hand, we don't need that variable name ace_of_spades. We could remove the name:

del(ace_of_spades)

But there is no need to use the name in the first place. We can simply do:

hand.add(Card(Card.RANK_ACE, Card.SUIT_SPADES))

This creates the card, then immediately passes the newly created card to Hand.add() to add it to a hand.

You don't, strictly speaking, need to store the ranks and suits inside the Card class. In Python, it might be more common to put these things outside of the classes, in "module" scope. I would most likely do:

RANK_ACE = 'A'
RANK_2 = '2'
# and so on
SUIT_CLUBS = 'c'
# and so on

class Card(object):
    # and so on

But if you are taking a class and your teacher wants you to put constants inside your programs' classes, then do that.

EDIT: You are trying to figure out what self.add() does in the program.

Here's the source code for it:

# inside class Hand
def add(self, card):
    self.cards.append(card)

self refers to the object. To see how this works, let's imagine you have an instance of class Hand, saved in the variable h. If you call h.add(ace_of_spades) then the self in this function will be set to the same instance of Hand that h refers to.

In fact, the call h.add(ace_of_spades) is exactly the same as doing this call:

Hand.add(h, ace_of_spades)

When you use the form h.add(), Python automatically does the same thing as the above call. Python looks at h and figures out that it is an instance of class Hand. Then Python looks in class Hand for a function called add(). Then Python builds a call similar to the one shown above.

self.cards refers to a list object stored inside the Hand instance. Python lists have a method function, .append(), that appends an item to the list.

You can read more about classes and method functions here: http://docs.python.org/2/tutorial/classes.html

Upvotes: 4

bcorso
bcorso

Reputation: 47138

Q1: Does (Card(rank, suit)) get sent to the add method straight away or does it get sent to the Card class first and then go to the add method?

Here's what's happening in self.add(Card(rank, suit)):

  1. Card(rank, suit) calls Card's __new__ and __init__ methods. __new__ creates a new Card object and the __init__ method initializes it to have the given rank an suit.
  2. That new card is passed to Deck.add() method where it is appended to self.cards list (Note that Deck inherits the cards list and add method from Hand)

Here I've stripped the code down to only the necessary methods for reference:

class Card(object):
    def __init__(self, rank, suit):
        self.rank = rank 
        self.suit = suit

class Hand(object):
    def __init__(self):
        self.cards = []
    def add(self, card):
        self.cards.append(card)

class Deck(Hand):
    def populate(self):
        for suit in Card.SUITS:
            for rank in Card.RANKS: 
                self.add(Card(rank, suit))   

Q2: The add method has a 'card' parameter, what gets put into that parameter?

The newly created Card as described above

Upvotes: 2

perreal
perreal

Reputation: 98078

If you add two print statements like these:

In Card class:

def __init__(self, rank, suit):
    print "card.init: {} {}".format(rank, suit)
    self.rank = rank 
    self.suit = suit

In Hand:

def add(self, card):
    print "add: {}".format(card)
    self.cards.append(card)

You will see this output:

Created a new deck.
Deck:
<empty>
card.init: A c
add: Ac
card.init: 2 c
add: 2c
card.init: 3 c
add: 3c

Upvotes: 1

s.gray
s.gray

Reputation: 188

Good question. You are definitely making sense :)

Here's what's happening in self.add(Card(rank, suit)): note that self.add(Card(rank, suit)) is being called from within a nested for loop. The rank and suit parameters are defined by those loops. You are right, Card(rank, suit) is evaluated first, then the returned card object is fed as a parameter to self.add method on the Deck. So, the combination of loops and method calls populates the deck with 52 card objects -- one of each suit and face. Does that make sense?

Upvotes: 2

Related Questions