Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to debug OOP Class/Method in Python

Tags:

python

oop

I'm attempting the canonical pedagogical programming exercise of building a program to simulate a card game. This is how far I am:

 class Card:

     def __init__(self, suit, rank):
         self.suit = suit
         self.rank = rank
         self.card = str(self.suit) + " " + str(self.rank)

 class Deck:

     def __init__(self):
         self.deck = []

     def Deck_Maker(self):
         ranks = range(1, 11) + ["J", "Q", "K", "A"]
         suits = ["Club", "Heart", "Spade", "Diamond"]

         return self.deck.append(Card(suits[0], ranks[0]))

Obviously I'm not done, I have to loop over suits and ranks to construct the deck. However, before continuing I want to check that I'm on track. To do this I want to print the results after invoking the Deck_Maker method. I have attempted to do this with the below code...

 def __str__(self):
         d = self.deck
         d2 = d.Deck_Maker()
         return str(d2)

Unfortunately, print(Deck().Deck_Maker()) returns None.

What am I doing wrong? More generally, how do programmers poke around within Classes and Methods while they make them?

Thanks!

like image 603
Jacob H Avatar asked Feb 06 '16 04:02

Jacob H


3 Answers

The append method on a list returns None so this is expected. Try splitting up your lines:

deck = Deck()
deck.Deck_Maker()
print(deck)

or change the method Deck_Maker to be:

def Deck_Maker(self):
    ranks = range(1, 11) + ["J", "Q", "K", "A"]
    suits = ["Club", "Heart", "Spade", "Diamond"]
    self.deck.append(Card(suits[0], ranks[0]))
    return self

Edit: There is also a problem in your str method. When you set d to self.deck you are setting it to be a list. The list doesn't know anything about the method Deck_Maker. Try changing to the method below. This will turn your list into a string. To make it more readable, you will probably also want to add a str method to the card class.

def __str__(self):
    return str(self.deck)
like image 149
Zack Graber Avatar answered Oct 13 '22 01:10

Zack Graber


I know it doesn't sound super smart, but a good debuger, with breakpoints, helps. You can actually see the flow of execution.

The bumpy issues are mro (method resolution order along the inheritance chain), class methods (does the result comes from instance/class), and metaclasses (class that creates class with type). To understand these issues, initialisation and what object provided the result, drop a logging or a print statement in every init , and on methods, that says "enters method foo from object baz". This will make things clearer.

like image 33
Aviah Laor Avatar answered Oct 12 '22 23:10

Aviah Laor


A few suggestions:

One change to Card I'd suggest: instead of having an attribute card, initialized in __init__, remove that last line from __init__ and add a __str__ method:

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

    def __str__(self): 
        return str(self.suit) + " " + str(self.rank)

Now every Card knows how to represent itself as a str on demand. You might add a __repr__ method, instead of or in addition to __str__:

    def __repr__(self): 
        return 'Card(%s, %s)' % (self.suit, self.rank)

The Deck class

You can also add a __str__ method to the Deck class.

I've renamed the Deck_Maker method to make_Deck, and I've made it a classmethod, because the name suggests that you mean it to be a "factory method", callable on the Deck class rather than on Deck instances. The method now does need to return the Deck it creates.

I also renamed the deck member variable to cards, since after all it is a list of Cards, and we will want to use deck to refer to Decks :)

Finally, I made the lists of suits and ranks class attributes, rather than locals of make_Deck, on the theory that eventually some other method will want to use them. In Python 3, range returns an iterable, hence list() around it.

A couple of issues: I'm not sure what cards of rank 1 are, given that you have "A" for aces; 2 through 11 seems more likely. (Thus, Decks presently have 56 cards :) Also, it will prove awkward to have both ints and strs as ranks: surely you'll want to compare ranks, and this representation will fight you. (Solution: store ranks as int only, and convert in __str__ and __repr__ methods.) You may want rank comparison to be a Card method. In that case, _ranks and _suits should be accessible to that class too (module-globals, for example).

But these rank and suit problems are for another day, and I'll leave the definitions basically unchanged.

class Deck():
    _ranks = list(range(1, 11)) + ["J", "Q", "K", "A"]
    _suits = ["Club", "Heart", "Spade", "Diamond"]

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

    @classmethod
    def make_Deck(cls):
        """cls is Deck"""
        deck = cls()    # make a Deck
        for s in cls._suits:
            for r in cls._ranks:
                deck.cards.append(Card(s, r))
        return deck

    # A basic example:
    def __str__(self):
        return ', '.join([str(card) for card in self.cards])

Now you can use code like the following:

>>> deck = Deck.make_Deck()
>>> print(deck)               # calls deck.__str__

which prints the str representations of all 56 (!) cards in deck, comma-separated and all on one line:

Club 1, Club 2, Club 3, Club 4, Club 5, Club 6, Club 7, Club 8, Club 9, Club 10, Club J, Club Q, Club K, Club A, Heart 1, Heart 2, Heart 3, Heart 4, Heart 5, Heart 6, Heart 7, Heart 8, Heart 9, Heart 10, Heart J, Heart Q, Heart K, Heart A, Spade 1, Spade 2, Spade 3, Spade 4, Spade 5, Spade 6, Spade 7, Spade 8, Spade 9, Spade 10, Spade J, Spade Q, Spade K, Spade A, Diamond 1, Diamond 2, Diamond 3, Diamond 4, Diamond 5, Diamond 6, Diamond 7, Diamond 8, Diamond 9, Diamond 10, Diamond J, Diamond Q, Diamond K, Diamond A
like image 32
BrianO Avatar answered Oct 12 '22 23:10

BrianO