“Class” versus “namedtuple” to simulate a deck in Python - python

Several books (or tutorials) define a card and a deck in the following fashion:
import random
class Card(object):
""" A card object with a suit and rank."""
RANKS = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)
SUITS = ('Spades', 'Diamonds', 'Hearts', 'Clubs')
def __init__(self, rank, suit):
"""Creates a card with the given rank and suit."""
self.rank = rank
self.suit = suit
def __str__(self):
"""Returns the string representation of a card."""
if self.rank == 1:
rank = 'Ace'
elif self.rank == 11:
rank = 'Jack'
elif self.rank == 12:
rank = 'Queen'
elif self.rank == 13:
rank = 'King'
else:
rank = self.rank
return str(rank) + ' of ' + self.suit
import random
class Deck(object):
""" A deck containing 52 cards."""
def __init__(self):
"""Creates a full deck of cards."""
self._cards = []
for suit in Card.SUITS:
for rank in Card.RANKS:
c = Card(rank, suit)
self._cards.append(c)
def shuffle(self):
"""Shuffles the cards."""
random.shuffle(self._cards)
def deal(self):
"""Removes and returns the top card or None
if the deck is empty."""
if len(self) == 0:
return None
else:
return self._cards.pop(0)
def __len__(self):
"""Returns the number of cards left in the deck."""
return len(self._cards)
def __str__(self):
"""Returns the string representation of a deck."""
result = ''
for c in self._cards:
result = self.result + str(c) + '\n'
return result
A recent book I am reading defines it as:
import collections
Card = collections.namedtuple('Card', ['rank', 'suit'])
class FrenchDeck:
ranks = [str(n) for n in range(2, 11)] + list('JQKA')
suits = 'spades diamonds clubs hearts'.split()
def __init__(self):
self._cards = [Card(rank, suit) for suit in self.suits
for rank in self.ranks]
def __len__(self):
return len(self._cards)
def __getitem__(self, position):
return self._cards[position]
If nothing else, this version “seems” less verbose. (But it is not my concern for this question. As they are, it is wrong to compare the length of the codes.)
For this example, and perhaps in general, what are the pros and cons of defining a card as namedtuple versus class?
If the answer is simply one is mutable and other is not, what are my reasons to care about that?
Is one version more Pythonic than the other?

The named tuple really is only less verbose in that you don't need the boilerplate __init__ method that the class has.
OK, so the implementation you show doesn't have a lengthy __str__ function either, but then again its representation as a string doesn't have the features required of the class version, so it's not reasonable to compare the amounts of code.
The important difference between the two is that namedtuple gives you immutable objects, whereas the class shown above is mutable (and would require extra code to make it immutable).
Extra functions (as khelwood mentions in a comment) can for example be dealt with by combining the two:
class Card(collections.namedtuple('CardBase', ['rank', 'suit'])):
def __str__(self):
# code to say "Ace of spades" goes here
The result still has read-only .rank and .suit attributes, although it does now has its own dictionary for other mutable attributes so it's not really an immutable type any more. If you're intending to mix read-only with read-write attributes then you're probably better off using #property than using namedtuple, but if you just want to stick some convenience functions on something that's otherwise a good fit for namedtuple, then this works.
A final possible disadvantage of using namedtuple is that the result is a tuple. That is to say, it can be accessed using [0] and [1], card objects can be added together using + with meaningless results, and everything else tuples do. It isn't usually actively harmful to have nonsense/irrelevant operations on your objects, but it's not good either because it can bloat your auto-generated documentation, make mistakes harder to find, and other such annoyances. It's also harder to change a published interface with at lot of guff in it, because once you publish it someone might use it.

Related

This is the code that's leading up to my class-based card game "Go Fish", but I cannot figure out how to do __str__ method for my Deck/Player class

'''
I am trying to figure out how to do the str method on my Deck and Player class. I can print an individual instance of my Card class, but that is it. Once I start trying to print my deck, I get an error code telling me that the list index is out of range. I know I am making a noob mistake somewhere and just don't realize it. If I can get these methods done, I can go ahead and code the game. I will put up all the code necessary to help if someone is up to it. Also need a str method for showing a player's hand too I guess. Here is the error code:
Traceback (most recent call last):
File "c:\Users\kille\OneDrive\Desktop\HW9\revised_classes.py", line 139, in
print(deck)
File "c:\Users\kille\OneDrive\Desktop\HW9\revised_classes.py", line 67, in str
vis.append(str(card))
File "c:\Users\kille\OneDrive\Desktop\HW9\revised_classes.py", line 16, in str
return f"{Card.values[self.value]}{Card.suits[self.suit]}"
IndexError: list index out of range
'''
class Card:
#This class represents a playing card.
suits = ['\u2666', '\u2665', '\u2663', '\u2660']
values = ['2', '3', '4', '5', '6', '7', '8', '9'
'10', 'J', 'Q', 'K', 'A']
def __init__(self, value, suit):
#Initialize attributes of card class.
self.value = value
self.suit = suit
# This works, but if it can be done better I am up for suggestions
def __str__(self):
#Returns a card
return f"{Card.values[self.value]}{Card.suits[self.suit]}"
def __repr__(self):
#Learning about this one
return f"{Card.values[self.value]}{Card.suits[self.suit]}"
def __eq__(self, card2):
#equal operator, compares the values of the cards
if self.value == card2.value:
return self.value == card2.value
else:
return False
def get_value(self):
#This function returns the value of a card
value = self.value
return print(value)
def get_suit(self):
#This function returns the suit of a card
return print(self.suit)
def same_value(self, card):
#This function checks to see if two cards have the same value.
if self.value == card.value:
return True
else:
return False
def same_suit(self, card):
#This function checks to see if two cards have the same suit.
if self.suit == card.suit:
return True
else:
return False
class Deck:
#This class represents a deck of playing cards.
def __init__(self):
#Initialize attributes for the Deck class
self.deck = []
for suit in range(4):
for value in range(13):
self.deck.append(Card(value, suit))
self.shuffle_deck()
#This is where I need to print the deck, but haven't figured it out yet
def __str__(self):
#This function returns a string representation of the deck.
vis = []
for card in self.deck:
vis.append(str(card))
return vis
def __len__(self):
#This function returns the length of the deck
return len(self.deck)
def add_card(self, card):
#This function adds a card to the deck
self.deck.append(card)
def shuffle_deck(self):
#This function shuffles the deck of playing cards
random.shuffle(self.deck)
def draw_card(self):
#This function allows you to draw a card from the deck
drawn_card = self.deck.pop()
print(drawn_card)
return drawn_card
def gt_rd_card(self):
#This function gets a random card from the deck
return random.choice(self.deck)
class Player(Deck):
#This class represents a player in a game and inherits from the Deck class
def __init__(self, name):
#Initialize attributes of the Player(Deck) class
self.name = name
self.wins = 0
self.pairs = []
self.hand = []
deck = Deck()
#This is where I need to figure out how to print a player's hand
def __str__(self):
#This allows me to print
return self.name + ': ' + ' '.join([str(card) for card in self.deck])
def get_name(self):
#This function returns the name of the player
return self.name
def get_wins(self):
#This function allows me to get the wins
return self.wins
def draw_card(self):
#This function allows a player to draw a card
self.hand.append(deck.draw_card)
return self.hand
# I also need to set up deal_hand(which will be 7 cards) and figure out how to print that
The problem lies in the assignment of Card.values: there is no , separation between '9' and '10', resulting in the merging of the two strings into '910', so the actual value list is shorter than you think.
In fact, it's easy to find this problem, you just need to traverse the Deck.deck and print the card, and you'll find 910♦ existence.
For method Deck.__ str__, a simpler implementation is to return str(self.deck) or repr(self.deck)

Can someone help me create a deck of Cards for a poker game in pygame

Im trying to make a poker game with in python using pygame and OOP. I previously made a text based blackjack game from a udemy course and im trying to use some of the same principles to create my deck but its not working. The problem I have is I want to create 52 card objects and I want each card object to have three attributes (suit, rank, and a png file).
class Card:
def __init__(self, suit, rank, pic):
self.suit = suit
self.pic = pic
self.rank = rank
class Deck:
def __init__(self):
self.deck_comp = []
def create_deck(self):
for suit in suits:
for rank in ranks:
for pic in deck:
self.deck_comp.append(Card(suit, rank, pic))
I have a feeling the three for loops are the problem. In the text based blackjack game the card only needed to have two attributes. For this game I need the card objects to have a picture and a value and suit so I can display them and compare them.
suit is a list of the four suit strings
Rank is a list of card names as strings and
pic is a list of 52 .png files (one for each card in the deck)
Would it not be wiser to hold all the png names in a dictionary, and assign an image in the Card class?
class Card:
def __init__(self, value, suit):
self.value = value
self.suit = suit
self.img = png_images[f'{value}{suit}']
class Deck:
def __init__(self, shuffle_cards=True):
self.cards = []
self.shuffle_cards = shuffle_cards
self.create()
def create(self):
for _ in range(number_of_decks):
for val in (2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14):
for suit in ("Hearts", "Spades", "Clubs", "Diamonds"):
self.cards.append(Card(val, suit))
if self.shuffle_cards:
shuffle(self.cards)
Basing on the example from one of the books:
import collections
Card = collections.namedtuple('Card', ['rank', 'suit'])
class Deck:
ranks = [str(n) for n in range(2, 11)] + ['JQKA']
suits = ['spades', 'diamonds', 'clubs', 'hearts']
# dictionary of dictionaries which maps ranks and suits to the picture
pic_mapping = {
'spades': {
'2': 'spades2',
'3': 'spades3'
...
},
'hearts': {
'2': 'hearts2',
'3': 'hearts3'
...
},
def __init__(self):
self._cards = [Card(rank, suit, pic_mapping[suit][rank]) for suit in self.suits
for rank in self.ranks]
Note that lists may become tuples or can be built using generators. Lists are just used for better readability.
In addition you can overload __len__ and __getitem__ to support operations such as:
indexing [i]
random.choice(collection)
slicing
iterating
reversed(collection)
Instead of dict mapping, you can use images naming convention to follow
this pattern f'{suit}{rank}' and dynamically add it to the Card object.
It's been a while since I've done something like this, but you could create a pickle dictionary, for each suit maybe.
Have a look at this:
How can I use pickle to save a dict?
And creating TRIES:
How to create a TRIE in Python
Hope it helps :)

How to make a global variable a string? [duplicate]

This question already has answers here:
How to print instances of a class using print()?
(12 answers)
What is the difference between __str__ and __repr__?
(28 answers)
Closed 3 years ago.
(Sorry if the question is somewhat vague, I'm not sure of a better title)
I'm not sure what it's called when you get something similar to the following:
[<main.Card object at 0x00350490>, <main.Card object at 0x00350590>]
[<main.Card object at 0x00350510>, <main.Card object at 0x003501B0>]
but I'm trying to print out the more readable format. I want to make sure things are in the correct order before I go more into changing the order.
import random as rd
class Card:
card_rank = [str(n) for n in range(2, 10)]
card_rank.extend(['Ten', 'Jack', 'Queen', 'King', 'Ace'])
card_suit = ['Spades', 'Clubs', 'Diamonds', 'Hearts']
def __init__(self, rank, suit):
assert 2 <= rank <= 14 and 1 <= suit <= 4
self.rank = rank
self.suit = suit
def __str__(self):
return '{} of {}'.format(Card.card_rank[self.rank - 2], Card.card_suit[self.suit - 1])
class Deck:
def __init__(self):
self.cards = [Card(rank, suit) for rank in range(2, 14 + 1) for suit in range(1, 4 + 1)]
class Player:
def __init__(self):
self.hand = []
def build_hand(self, card):
self.hand.append(card)
def __str__(self):
return self.hand
class Dealer(Deck, Player):
def deck_shuffle(self):
rd.shuffle(self.cards)
def deck_deal(self):
single_card = self.cards.pop()
print(single_card)
return single_card
dealer = Dealer()
player_hand = Player()
dealer_hand = Player()
dealer.deck_shuffle()
player_hand.build_hand(dealer.deck_deal())
dealer_hand.build_hand(dealer.deck_deal())
player_hand.build_hand(dealer.deck_deal())
dealer_hand.build_hand(dealer.deck_deal())
print(player_hand.hand)
print(dealer_hand.hand)
I am pretty sure this is a very obvious thing that I should be able to realize on my own, but the trial and error has only resulted in error. What is it that I'm doing that creates the unreadable format and why doesn't str() or str work here?
The __str__ method for lists doesn't recursively call str on the elements in the list; it just uses the __repr__. If you want a better display of a hand, generate it yourself:
print(" ".join([str(c) for c in player_hand.hand))
print(" ".join([str(c) for c in dealer_hand.hand))

Appending copys of objects in python [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 4 years ago.
Improve this question
I'm trying to sort through a list "cards" in a player's hand, and create a new list of "cards" containing only those cards which are spades:
class Card(object):
def __init__(self, value, suit):
self.value = value
self.suit = suit
class Player(object):
def __init__(self, hand):
self.hand = hand # hand is a list of 13 card objects
def spades(self):
spades = []
for card in self.hand:
if card.suit == 'spade':
spades.append(copy.deepcopy(card))
return spades
however, calling the spades() method outputs a list of cards of the same number and value of the last card in the hand, and the same length of the amount of spades. for instance:
players = deal()
for card in players[1].hand:
print(str(card.value) + ' of ' + card.suit)
print("SPADES")
spades = players[1].spades()
for cards in spades:
print(str(card.value) + 'of' + card.suit)
outputs:
6ofclub
13ofheart
4ofdiamond
7ofspade
13ofdiamond
13ofclub
11ofspade
8ofdiamond
3ofdiamond
10ofheart
8ofheart
12ofclub
12ofdiamond
SPADES
12ofdiamond
12ofdiamond
I think realize that the list "spades" is appending pointers to the variable "card", rather than new objects. However I thought that using copy or deep copy would have solved this issue.
From what I see, there is no problem in the code. The issue is with the print at the end...
You've written:
for cards in spades:
print(str(card.value) + 'of' + card.suit)
So you're looping and getting cards but you're printing card which is from the previous for loop which is still assigned the variable from the last for loop's last iteration
Here's the entire code to reproduce the fix:
import copy
class Card(object):
def __init__(self, value, suit):
self.value = value
self.suit = suit
def __str__(self):
return str(self.value) + " of " + str(self.suit)
class Player(object):
def __init__(self, hand):
self.hand = hand # hand is a list of 13 card objects
def spades(self):
spades = []
for card in self.hand:
if card.suit == 'spade':
spades.append(copy.deepcopy(card))
return spades
vals = [i.split("of", 1) for i in """6ofclub
13ofheart
4ofdiamond
7ofspade
13ofdiamond
13ofclub
11ofspade
8ofdiamond
3ofdiamond
10ofheart
8ofheart
12ofclub
12ofdiamond""".strip().split()]
hand = [Card(i[0], i[1]) for i in vals]
players = [None, Player(hand)]
for card in players[1].hand:
print(card)
print()
spades = players[1].spades()
print("SPADES")
for cards in spades:
print(cards) # <----- Print card*s* not card.
Found a solution by changing the spades() method but not entirely sure why it worked:
def spades(self):
spades = []
for card in self.hand:
if card.suit == 'spade':
copy = deepcopy(card)
spades.append(copy)
return spades
I assigned the deep copy to a variable first, then passed the variable through append(). This seems like a trivial change to me...if anyone has an explanation of this behavior it would be much appreciated.

Remove an object from a list of objects

I basically have 3 classes. Card, Deck, and Player. The Deck is a list of cards. I am trying to remove a card from the deck. But I am getting a ValueError saying that the card is not in the list. From my understanding, it is and I am passing the correct object through the removeCard function. I am not sure why I am getting a ValueError. So in short, the problem is that I need to remove an object (Card) from a list of Cards.
My issue is that when I try to remove a card from the deck I get an error like this:
ValueError: list.remove(x): x not in list
This is what I have so far:
Card class:
import random
class Card(object):
def __init__(self, number):
self.number = number
Deck class (the error is thrown here, in the removeCard function):
class Deck(object):
def __init__(self):
self.cards = []
for i in range(11):
for j in range(i):
self.cards.append(Card(i))
def addCard(self, card):
self.cards.append(card)
def removeCard(self, card):
self.cards.remove(card)
def showCards(self):
return ''.join((str(x.number) + " ") for x in self.cards)
Player class:
class Player(object):
def __init__(self, name, hand):
self.name = name
self.hand = hand
main function:
def main():
deck = Deck()
handA = [Card(6), Card(5), Card(3)]
handB = [Card(10), Card(6), Card(5)]
playerA = Player("A", handA)
playerB = Player("B", handB)
print("There are " + str(len(deck.cards)) + " cards in the deck.")
print("The deck contains " + deck.showCards())
for i in handA:
deck.removeCard(i)
print("Now the deck contains " + deck.showCards())
main()
When you call list.remove, the function searches for the item in the list, and deletes it if found. When searching, it needs to perform a comparison, comparing the search item to every other list item.
You're passing an object to remove. A user defined object. They do not behave the same way as, say, integers would, when performing comparisons.
For example, object1 == object2, where object* are objects of the Card class, by default are compared against their unique id values. Meanwhile, you want a comparison to be performed against the card number, and removal done accordingly.
Implement an __eq__ method in your class (python-3.x) -
class Card(object):
def __init__(self, number):
self.number = number
def __eq__(self, other):
return self.number == other.number
Now,
len(deck.cards)
55
for i in handA:
deck.removeCard(i)
len(deck.cards)
52
Works as expected. Note that in python-2.x, you'd implement __cmp__ instead.

Categories

Resources