Python Forum
Classes, OOP, building deck of 52 playing cards
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Classes, OOP, building deck of 52 playing cards
#1
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:

Error:
$ 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:

Error:
$ 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:

Error:
$ 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?
Reply
#2
Maybe something like this. From your first example

class Card:
   def __init__(self):
       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()

deck = [(card, value) for card in cards.suits for value in cards.value]
print(deck)
Output:
[('Hearts', 'A'), ('Hearts', '2'), ('Hearts', '3'), ('Hearts', '4'), ('Hearts', '5'), ('Hearts', '6'), ('Hearts', '7'), ('Hearts', '8'), ('Hearts', '9'), ('Hearts', '10'), ('Hearts', 'J'), ('Hearts', 'Q'), ('Hearts', 'K'), ('Diamonds', 'A'), ('Diamonds', '2'), ('Diamonds', '3'), ('Diamonds', '4'), ('Diamonds', '5'), ('Diamonds', '6'), ('Diamonds', '7'), ('Diamonds', '8'), ('Diamonds', '9'), ('Diamonds', '10'), ('Diamonds', 'J'), ('Diamonds', 'Q'), ('Diamonds', 'K'), ('Clubs', 'A'), ('Clubs', '2'), ('Clubs', '3'), ('Clubs', '4'), ('Clubs', '5'), ('Clubs', '6'), ('Clubs', '7'), ('Clubs', '8'), ('Clubs', '9'), ('Clubs', '10'), ('Clubs', 'J'), ('Clubs', 'Q'), ('Clubs', 'K'), ('Spades', 'A'), ('Spades', '2'), ('Spades', '3'), ('Spades', '4'), ('Spades', '5'), ('Spades', '6'), ('Spades', '7'), ('Spades', '8'), ('Spades', '9'), ('Spades', '10'), ('Spades', 'J'), ('Spades', 'Q'), ('Spades', 'K')] [Finished in 0.086s]
Your 2nd example:
#! /usr/bin/emv python3

class Card:
    def __init__(self):
       self.suits = ["Hearts", "Diamonds", "Clubs", "Spades"]
       self.value = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]
       self.deck = [(card, value) for card in self.suits for value in self.value]

    def __repr__(self):
        return f'Cards -> {self.deck}'

print(Card())
Output:
Cards -> [('Hearts', 'A'), ('Hearts', '2'), ('Hearts', '3'), ('Hearts', '4'), ('Hearts', '5'), ('Hearts', '6'), ('Hearts', '7'), ('Hearts', '8'), ('Hearts', '9'), ('Hearts', '10'), ('Hearts', 'J'), ('Hearts', 'Q'), ('Hearts', 'K'), ('Diamonds', 'A'), ('Diamonds', '2'), ('Diamonds', '3'), ('Diamonds', '4'), ('Diamonds', '5'), ('Diamonds', '6'), ('Diamonds', '7'), ('Diamonds', '8'), ('Diamonds', '9'), ('Diamonds', '10'), ('Diamonds', 'J'), ('Diamonds', 'Q'), ('Diamonds', 'K'), ('Clubs', 'A'), ('Clubs', '2'), ('Clubs', '3'), ('Clubs', '4'), ('Clubs', '5'), ('Clubs', '6'), ('Clubs', '7'), ('Clubs', '8'), ('Clubs', '9'), ('Clubs', '10'), ('Clubs', 'J'), ('Clubs', 'Q'), ('Clubs', 'K'), ('Spades', 'A'), ('Spades', '2'), ('Spades', '3'), ('Spades', '4'), ('Spades', '5'), ('Spades', '6'), ('Spades', '7'), ('Spades', '8'), ('Spades', '9'), ('Spades', '10'), ('Spades', 'J'), ('Spades', 'Q'), ('Spades', 'K')]
Drone4four and BashBedlam like this post
I welcome all feedback.
The only dumb question, is one that doesn't get asked.
My Github
How to post code using bbtags


Reply
#3
Do you understand what scope is? self is only in scope inside a class, not outside it, as lines 10 and 11 are in the first piece of code.
Drone4four likes this post
Reply
#4
What is the point of having class Deck when not using it (it is just pass), but using list instead?
Drone4four likes this post
If you can't explain it to a six year old, you don't understand it yourself, Albert Einstein
How to Ask Questions The Smart Way: link and another link
Create MCV example
Debug small programs

Reply
#5
I don't think suits or values make any sense as instance attributes. These should be class attributes because they are the same for every card. A card has a suit and a value, but not every card needs the list of all suits or list of all values. You also want to be able to access these attributes with there being an instance, something that is impossible with instance variables.

Deck will have to know how to make cards. In addition to being a list it should do "deck-things" like shuffle and deal.
import random

class Card:
    """A playing card with a suit and value"""
    # These are attributes of class Card.  You can access these using Card.suits or Card.values
    suits = ["Hearts", "Diamonds", "Clubs", "Spades"]
    values = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]

    def __init__(self, suit, value):
        self.suit = suit      # These are attributes of an instance of Card.  You need an instance to use them.  Card("Hearts", "A").suit or Card("Clubs", "K").value
        self.value = value

    def __repr__(self):
        """Return a string representation of myself"""
        return f"{self.value} {self.suit}"

class Deck:
    """A list of cards that knows how to shuffle and deal"""
    def __init__(self, shuffle=False):
        self.cards = [Card(suit, value) for suit in Card.suits for value in Card.values]  # Card.suits is how you reference the suits class variable in Cards
        if shuffle:
            random.shuffle(self.cards)

    def __len__(self):
        """How many cards are in deck"""
        return len(self.cards)

    def deal(self, count=1):
        """Deal count cards.  Returns a list of cards"""
        cards = self.cards[:count]
        self.cards = self.cards[count:]
        return cards

deck = Deck(shuffle=True)
print(len(deck))  # Should have 52 cards
cards = deck.deal(5)
print(len(deck))  # Should have 47 cards after dealing 5
print(cards)
Output:
52 47 [3 Diamonds, K Spades, Q Spades, 6 Clubs, 5 Diamonds]
Drone4four likes this post
Reply
#6
(Dec-30-2021, 06:17 PM)deanhystad Wrote: I don't think suits or values make any sense as instance attributes.
It doesn't make sense for them to be class attributes of class Card either. It may make sense for Deck though.
Drone4four likes this post
If you can't explain it to a six year old, you don't understand it yourself, Albert Einstein
How to Ask Questions The Smart Way: link and another link
Create MCV example
Debug small programs

Reply
#7
These exercises with cards and decks always remind me the book that taught me C in the 1980s Rolleyes , it was 'A book on C' by Al Kelley and Ira Pohl Heart . It contained a case study with a deck of cards and the value of poker hands. It seems that the book still exists, they are selling the 4th edition!
Drone4four likes this post
Reply
#8
I don't know about suits and values being in deck makes more or less sense than them being attributes of cards. Cards have a suit and a value, and it seems reasonable that the card should know what makes for a valid suit and value. You might want to make a Card("KS") and have the card able to translate this into ("King", "K", 13, "Spades", "S") attributes for the card. This would be difficult to do without know anything about suits or values.

Maybe the deck should know nothing about cards other than you can ask for a generator that will return cards so you could do something like:
class Deck:
    def __init__(self, card_generator, decks=1):
        self.cards []
        for deck in range(decks):
            self.cards += list(card_generator())
I think where suit and value lists should go depends more on the problem you are trying to solve than an innate card-ness or deck-ness.
Drone4four likes this post
Reply
#9
Using Enum and dataclass
from enum import Enum
from dataclasses import dataclass, field


class Suit(Enum):
    HEART = "Heart"
    DIAMOND = "Diamond"
    CLUB = "Club"
    SPADE = "Spade"


class Value(Enum):
    ACE = "Ace"
    TWO = "Two"
    THREE = "Three"
    FOUR = "Four"
    FIVE = "Five"
    SIX = "Six"
    SEVEN = "Seven"
    EIGHT = "Eight"
    NINE = "Nine"
    TEN = "Ten"
    JACK = "Jack"
    QUEEN = "Queen"
    KING = "King"


@dataclass
class Card:
    value: Value
    suit: Suit


@dataclass
class Deck:
    cards: list[Card] = field(default_factory=list)


def create_standard_deck() -> Deck:
    cards = [Card(value=value, suit=suit) for suit in Suit for value in Value]
    return Deck(cards=cards)


def main():
    deck = create_standard_deck()
    for card in deck.cards:
        print(card)


if __name__ == "__main__":
    main()
Output:
Card(value=<Value.ACE: 'Ace'>, suit=<Suit.HEART: 'Heart'>) Card(value=<Value.TWO: 'Two'>, suit=<Suit.HEART: 'Heart'>) Card(value=<Value.THREE: 'Three'>, suit=<Suit.HEART: 'Heart'>) Card(value=<Value.FOUR: 'Four'>, suit=<Suit.HEART: 'Heart'>) Card(value=<Value.FIVE: 'Five'>, suit=<Suit.HEART: 'Heart'>) Card(value=<Value.SIX: 'Six'>, suit=<Suit.HEART: 'Heart'>) Card(value=<Value.SEVEN: 'Seven'>, suit=<Suit.HEART: 'Heart'>) Card(value=<Value.EIGHT: 'Eight'>, suit=<Suit.HEART: 'Heart'>) Card(value=<Value.NINE: 'Nine'>, suit=<Suit.HEART: 'Heart'>) Card(value=<Value.TEN: 'Ten'>, suit=<Suit.HEART: 'Heart'>) Card(value=<Value.JACK: 'Jack'>, suit=<Suit.HEART: 'Heart'>) Card(value=<Value.QUEEN: 'Queen'>, suit=<Suit.HEART: 'Heart'>) Card(value=<Value.KING: 'King'>, suit=<Suit.HEART: 'Heart'>) Card(value=<Value.ACE: 'Ace'>, suit=<Suit.DIAMOND: 'Diamond'>) Card(value=<Value.TWO: 'Two'>, suit=<Suit.DIAMOND: 'Diamond'>) ... ... Card(value=<Value.QUEEN: 'Queen'>, suit=<Suit.SPADE: 'Spade'>) Card(value=<Value.KING: 'King'>, suit=<Suit.SPADE: 'Spade'>)
buran, Drone4four, ndc85430 like this post
Reply
#10
Hello Pythonistas!

I’m back. My apologies for the delay in my response.

Leveraging @menator01’s initial reply, I integrated it into one of my scripts. It looks like this:

class Card:
   def __init__(self):
       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) for card in self.suits for value in self.value]
 
   def __repr__(self):
       return f"This should be your list of cards (raw): {self.cards}."
 
class Deck:
  pass
When I loaded it into my try bpython repl, I began playing with instances of the class like so:

$ bpython
bpython version 0.22.1 on top of Python 3.9.9 /usr/bin/python
>>> import card_deck_exercise
>>> import random
>>> instance = card_deck_exercise.Card()
>>> instance.cards

[('Hearts', 'A'), ('Hearts', '2'), ('Hearts', '3'), ('Hearts', '4'), ('Hearts', '5'), ('Hearts', '6'), ('Hearts', '7'), ('Hearts', '8'), ('Hearts', '9'), ('Hearts', '10'), ('Hearts', 'J'), ('Hearts', 'Q'), ('Hearts', 'K'), ('Diamonds', 'A'), ('Diamonds', '2'), ('Diamonds', '3'), ('Diamonds', '4'), ('Diamonds', '5'), ('Diamonds', '6'), ('Diamonds', '7'), ('Diamonds', '8'), ('Diamonds', '9'), ('Diamonds', '10'), ('Diamonds', 'J'), ('Diamonds', 'Q'), ('Diamonds', 'K'), ('Clubs', 'A'), ('Clubs', '2'), ('Clubs', '3'), ('Clubs', '4'), ('Clubs', '5'), ('Clubs', '6'), ('Clubs', '7'), ('Clubs', '8'), ('Clubs', '9'), ('Clubs', '10'), ('Clubs', 'J'), ('Clubs', 'Q'), ('Clubs', 'K'), ('Spades', 'A'), ('Spades', '2'), ('Spades', '3'), ('Spades', '4'), ('Spades', '5'), ('Spades', '6'), ('Spades', '7'), ('Spades', '8'), ('Spades', '9'), ('Spades', '10'), ('Spades', 'J'), ('Spades', 'Q'), ('Spades', 'K')]

>>> random.shuffle(instance.cards)
>>> instance.cards

[('Diamonds', '10'), ('Diamonds', 'K'), ('Clubs', '5'), ('Hearts', '4'), ('Spades', '8'), ('Spades', 'A'), ('Hearts', 'J'), ('Hearts', '8'), ('Hearts', 'Q'), ('Hearts', '7'), ('Clubs', '4'), ('Spades', '2'), ('Diamonds', '8'), ('Spades', '5'), ('Diamonds', '7'), ('Clubs', '7'), ('Hearts', '3'), ('Diamonds', '3'), ('Spades', '4'), ('Clubs', '3'), ('Hearts', '5'), ('Diamonds', 'A'), ('Spades', 'Q'), ('Diamonds', 'J'), ('Clubs', '6'), ('Hearts', 'A'), ('Spades', '3'), ('Diamonds', '9'), ('Clubs', '9'), ('Hearts', '10'), ('Clubs', '2'), ('Spades', '6'), ('Hearts', '2'), ('Diamonds', '5'), ('Diamonds', '2'), ('Clubs', 'K'), ('Spades', '9'), ('Diamonds', '6'), ('Clubs', 'A'), ('Clubs', '8'), ('Spades', 'J'), ('Hearts', 'K'), ('Diamonds', 'Q'), ('Hearts', '6'), ('Clubs', 'J'), ('Clubs', '10'), ('Spades', '10'), ('Spades', 'K'), ('Spades', '7'), ('Hearts', '9'), ('Clubs', 'Q'), ('Diamonds', '4')]

>>> deal = instance.cards.pop(-1)
>>> deal
('Spades', '4')
As you can see, I managed to shuffle and deal using the random module and the pop built-in method. It’s great to see that I got it working.

However @deanhystad’s implementation with doc strings and all the methods and functionality integrated inside the two classes is very instructive. Thank you deanhystad for taking the time to write this script.

Reading @Yoriz’s implementation with dataclass and Enum is incredible.

Comparing my beginner attempt at modeling a card game with classes to all the Python veterans on this message board is humbling. Thank you to everyone who shared their implementations.
menator01 and BashBedlam like this post
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Running 3rd party libs on Steam Deck (Arch Linux) with restricted access metulburr 0 1,855 Jan-07-2023, 10:41 PM
Last Post: metulburr
  which design / pattern when building classes and subclasses Phaze90 2 1,125 Nov-19-2022, 08:42 AM
Last Post: Gribouillis
  Generating random business cards Inkanus 2 2,201 Dec-08-2020, 09:41 PM
Last Post: buran
  Simple cards game blackpanda 3 4,249 Apr-10-2020, 08:46 PM
Last Post: TomToad
  python program on cards marc237 3 2,767 Apr-27-2019, 09:26 PM
Last Post: ichabod801
  Using classes? Can I just use classes to structure code? muteboy 5 5,043 Nov-01-2017, 04:20 PM
Last Post: metulburr

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020