Classes, OOP, building deck of 52 playing cards

Hi Team Python!

I’m trying to build a deck of 52 playing cards using OOP and list comprehension.

Here are some scripts:

class Card:
   def __init__(self, suits, value):
       self.suits = ["Hearts", "Diamonds", "Clubs", "Spades"]
       self.value = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]
 
class Deck:
   pass
 
 
cards = [Card(self.value, self.suits) for self.value in range(1, 14) for suit in self.suits]
print(cards)

The traceback in my shell reads:

$ python card-deck-exercise.py
Traceback (most recent call last):
  File "/home/<user>/dev/projects/python/2018-and-2020/The-Modern-Python-3-Bootcamp-Colt-Steele-on-Udemy/OOP/card-deck-exercise.py", line 10, in <module>
    cards = [Card(self.value, self.suits) for self.value in range(1, 14) for suit in self.suits]
  File "/home/<user>/dev/projects/python/2018-and-2020/The-Modern-Python-3-Bootcamp-Colt-Steele-on-Udemy/OOP/card-deck-exercise.py", line 10, in <listcomp>
    cards = [Card(self.value, self.suits) for self.value in range(1, 14) for suit in self.suits]
NameError: name 'self' is not defined

This error tells me that Python doesn’t like the way I am calling or referring to the class attributes self when I attempt to instantiate at line 10. So I tried removing self which returned a name error saying that suits is not defined.

Here is another attempt, this time with the list comprehension embedded within the class __init__ method and called within a __repr__ method:

class Card:
   def __init__(self, suits, value, cards):
       self.suits = ["Hearts", "Diamonds", "Clubs", "Spades"]
       self.value = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]
       self.cards = [Card(value, suits) for value in range(1, 14) for suit in suits]
 
   def __repr__(self):
       return f"This should be your list of cards (raw): {self.cards}."
 
class Deck:
   pass
 
 
j = Card()
print(repr(j))

The traceback now shows:

$ python card-deck-exercise.py
]Traceback (most recent call last):
  File "/home/gnull/dev/projects/python/2018-and-2020/The-Modern-Python-3-Bootcamp-Colt-Steele-on-Udemy/OOP/card-deck-exercise.py", line 14, in <module>
    j = Card()
TypeError: __init__() missing 3 required positional arguments: 'suits', 'value', and 'cards'

This error tells me that when the Card class is called, Python is expecting positional arguments to be initialized or passed in. However as I understand this class, the attributes are ‘hard coded’ into the class definition so when instantiated, arguments shouldn’t be required to be passed in. So I am not sure why I am getting this particular traceback.

In my next iteration of my script I attempted to swap out the hard coded class attributes and try passing them in during instantiation like this:

class Card:
   def __init__(self, suits, value, cards):
       self.suits = suits
       self.value = value
       self.cards = cards
 
   def __repr__(self):
       return f"This should be your list of cards (raw): {self.cards}."
 
class Deck:
   pass

 
j = Card(self,["Hearts", "Diamonds", "Clubs", "Spades"],["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"], [Card(value, suits) for value in range(1, 14) for suit in suits])
print(repr(j))

The traceback now reads:

$ python card-deck-exercise2.py
Traceback (most recent call last):
  File "/home/gnull/dev/projects/python/2018-and-2020/The-Modern-Python-3-Bootcamp-Colt-Steele-on-Udemy/OOP/card-deck-exercise2.py", line 14, in <module>
    j = Card(self,["Hearts", "Diamonds", "Clubs", "Spades"],["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"], [Card(value, suits) for value in range(1, 14) for suit in suits])
NameError: name 'self' is not defined

With classes, the self argument is always a convention. It’s not really supposed to be defined. It’s just always included. And in my case, it’s included both in the class definition and when I attempt to instantiate. I tried removing the self argument from instantiation which then says suits is not defined.

I’m stabbing in the dark at this point. Can any one shed elaborate on what the tracebacks are trying to convey and what exactly I am doing wrong in each script iteration above?

In your last example, the issue is an additional argument. When creating an instance of a class (here, a j instance of Cards), you don’t need to use self:

# This should work
j = Cards(["Hearts", "Diamonds", "Clubs", "Spades"],["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"], [Card(value, suits) for value in range(1, 14) for suit in suits])

However, when calling a method without instances, you will have to provide a self. But don’t put a rough “self”! That argument is the class:

class MyClass:
    def __init__(self):
        pass
    def some_stuff(self):
        print("Hello folks!")

mc = MyClass()  # generated an instance
mc.some_stuff()  # `self` is not used here

# No instances, so we should provide a class (aka `self`).
# This is commonly used when programming subclasses of classes, to
# run parent methods under child classes...
MyClass.some_stuff(MyClass)

Your existing code has each and every card carry around an independent

copy of the list of suits and list of values.

The existing Card class accepts a suit and value argument, but ignores

them.

Move your list of suits and values into global constants outside of

the class:

SUITS = 'Hearts Diamonds Clubs Spades'.split()

VALUES = 'A 2 3 4 5 6 7 8 9 10 J Q K'.split()

A card should have one value and one suit, not a whole list of them.

Change your Card class to this:

class Card(object):

    def __init__(self, value, suit):

        self.value = value

        self.suit = suit



    # Give it a nice display.

    def __repr__(self):

        return 'Card(%r, %r)' % (self.value, self.suit)

Then a deck is just a list of cards:

deck = [Card(value, suit) for value in VALUES for suit in SUITS]

assert len(deck) == 52

print(deck)

And shuffle them:

import random

random.shuffle(deck)

print(deck)

Duty calls, and I have to step away from the computer now. But when I

return (tonight? tomorrow?) I will try to answer your question to

explain why you were getting the errors you are getting.

(Sorry, but fixing problems is often easier than explaining why they

were wrong in the first place.)