Deck of cards

Can someone please explain to me why I am getting the output below

class Card:
    def __init__(self, number, suit):
        self.number = number
        self.suit = suit
    def __repr__(self):
        return number + suit
deck = []

suits = ['diamnonds', 'clubs', 'hearts', 'spades']
numbers = ['A', 2, 3, 3, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K']

for suit in suits:
    for number in numbers:
        deck.append(Card(number, suit))
print(deck)

returns : [Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades, Kspades]

Well your print seems to work but the actual representation is off which suggests __repr__ is off. Have you double checked what names you refer to in this method?

It’s also worth testing the following since it’ll also cause issues-

x = 2 + "clubs"
2 Likes

Thank you for your response but I’m not quite sure what you’re asking. I’m trying to create a deck of cards and I don’t understand why it has just added the last item in each lrst I’m iterating through to Deck.

It’s nothing to do with the actual items in your list. The problem is with the names you’re using in your __repr__ method. Check carefully exactly what names you used here.

1 Like
  1. Add the following print statement
    def __init__(self, number, suit):
        print("init:", number, suit)   # <-- add print init
        self.number = number
        self.suit = suit

    def __repr__(self):
        print("repr:", number, suit)   # <--add print repr
        return number + suit
  1. Run your code without the last line: print(deck) , observe which print is displayed? init or repr
  2. Run your code with print(deck) and observer again the 2 print CONTENT.

BTW,

suits = [‘diamnonds’, ‘clubs’, ‘hearts’, ‘spades’]
numbers = [‘A’, 2, 3, 3, 5, 6, 7, 8, 9, 10, ‘J’, ‘Q’, ‘K’]

‘diamnonds’ vs ‘diamonds’
Why 2, 3 …10 not in quote?

I’m not sure what exactly returns your repl method.
But this will fix your problem:

class Card:
    def __init__(self, number, suit):
        self.number = number
        self.suit = suit
    def __repr__(self):
        return str(self.number) + self.suit

When an object is printed you get either the return value of its __repr__ function or its name and memory location.
In the above case an entire list of objects is created and then each one of them is printed. You saw earlier what value should be being returned vs what is being returned. When return number + suit those two variable names will be pulled from the for loops’ last values.

Thank you for the answer. But you obviously not right. To have correct result for each object, you should use its own instance variables. instead of number - self. number, and for the suit - self.suit. Otherwise you will get Kspades for all objects. In this case suit and number are only arguments to the constructor and don’t have a reasonable meaning in repl method, that’s why I write I don’t know what repl returns.

I don’t believe that’s what @8-bit-gaming is saying. What they said was that the values being returned from __repr__ are the same as those from the values of the last for loop.

All that happens is those names cannot be found in the local scope of that method so the surrounding namespaces are checked (e.g. class namespace is next) until those names are found in the global namespace. Since that method isn’t called until after the for loop is finished those values will be whatever they were after that loop finished. Functions behave the same way (search locals and then search outer nested scopes one by one). Obviously this is not what the OP intended.

As a further aside, considering the purpose here is just to create a pretty string it’s worth looking at the __str__ method too as there is a difference in the purpose of the __repr__ and __str__ dunder methods. Namely __str__ is a neat human readable output for created instance objects and __repr__ is designed to aid in debugging for developers-
https://docs.python.org/3/reference/datamodel.html#object.str
https://docs.python.org/3/reference/datamodel.html#object.repr

1 Like

@tgrtim is correct about my post. I wasn’t saying the OP should use number and suit as opposed to self.number and self.suit, just explaining why they got that result. Apologies if I was unclear.

This has all been enormously helpful. Thank you everyone for contributing. Now I am curious about another result I am getting. I am making a deal function for the cards, and I wanted the function to remove each card as it is dealt. This is what I originally had…

def deal(players, deck):
    random.shuffle(deck)
    for card in range(len(deck)):
        if card % 4 == 0:
            players[0].hand.append(deck.pop(card))
        elif card % 4 == 1:
            players[1].hand.append(deck.pop(card))
        elif card % 4 == 2:
            players[2].hand.append(deck.pop(card))
        elif card % 4 == 3:
            players[3].hand.append(deck.pop(card))
        else: return

The error message said "pop index out of range. I figured what I was doing wrong, but I still don’t understand why that is incorrect.

1 Like

Hello @plasker.

If you refer the list.pop(index) method definition available here, you’ll see that if you specify the index, the item in the list will be removed from that index, right? So, as you remove elements from the list, your deck list will be getting smaller and hence at each pop, the length of the list will decrease by 1. But, if you look at the for loop, it’s iterating from 0 to the length of the original list. So, do you see the problem now? The loop is iterating over the size of the original list and while the loop iterates the list is getting smaller and at one point, the value of card would be greater than the size of the list at that point since the list is getting smaller at each iteration. So, you’ll get an ‘index out of range’ error.

Does that help understand better?

I see now! Thank you. If you wouldn’t mind, I have another one for you!.

In the card game, the suits have different ranks. I am trying to define a gr method so that when the game is played if 2 cards with equal number values go up against each other, the one wit the superior suit will win. This is what I have:

type or paste code here
```suit_rank = [1, 2, 3, 4]
suits = ['diamnonds', 'clubs', 'hearts', 'spades']
numbers = ['A', 2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K']

class Card:
    def __init__(self, number, suit, suit_rank):
        self.number = number
        self.suit = suit
        self.suit_rank = suit_rank
       
    def __repr__(self):
        return str(self.number) + ' of ' + str(self.suit)
    
    def __gt__(self, other):
        if numbers.index(self.number) >= numbers.index(other.number):
            if self.suit_rank > other.suit_rank:
                return self > other

However, when I test it by printing the function:

if Jacob.hand[0] > Sophia.hand[0]:
    print('True')
else:
    print('nope')

The hand attribute trailing each player represents a list of card objects that are assigned to my player objects.
When the numbers of the card are the same I get the exception:
“RecursionError: maximum recursion depth exceeded in comparison”

Any idea why?

What do you think might be the reason for recursion? Have a close look at what you’re returning there.

So I changed my method to just return True, and that seems to make the test if function work, but I guess I’m still nut sure how these dunder methods such as add and gt really work. Could you please explain a little bit.

When you use operators such as + and > what you’re actually doing is calling the __add__ and __gt__ methods of an object respectively.

So for two objects, x and y the following happens-

x + y
# This is doing the following...
x.__add__(y)

So if you consider the __gt__ method you attempted to use the > operator in the return but that operator actually calls the method (which calls itself again and again…).

Thank you! and I have another one!

I am trying to make a function that sorts a players hand, which is a list of Card objects by it’s attribute suit_rank. This is what I have:

import random
import math

suit_rank = [1, 2, 3, 4]
suits = ['diamnonds', 'clubs', 'hearts', 'spades']
numbers = ['A', 2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K']

class Card:
    def __init__(self, number, suit, suit_rank):
        self.number = number
        self.suit = suit
        self.suit_rank = suit_rank
       
    def __repr__(self):
        return str(self.number) + ' of ' + str(self.suit)
    
    def __gt__(self, other):
        if numbers.index(self.number) > numbers.index(other.number):
            return True
        if numbers.index(self.number) == numbers.index(other.number):
            if self.suit_rank > other.suit_rank:
                return True
    
def __lt__(self, other):
        if numbers.index(self.number) < numbers.index(other.number):
            return True
        if numbers.index(self.number) == numbers.index(other.number):
            if self.suit_rank < other.suit_rank:
                return True

class Player:
    def __init__(self, name):
        self.name = name
        self.hand = []


class Deck(list):
    def __init__(self):
        
        for suit in suits:
            for number in numbers:
                self.append(Card(number, suit, suit_rank[suits.index(suit)]))
        random.shuffle(self)

Jacob = Player('Jacob')
Sophia = Player('Sophia')
Emily = Player('Emily')
Cory = Player('Cory')

game1 = Deck()


def deal(players, deck):
    random.shuffle(deck)
    for card in range(len(deck)):
        if card % 4 == 0:
            players[0].hand.append(deck.pop())
        elif card % 4 == 1:
            players[1].hand.append(deck.pop())
        elif card % 4 == 2:
            players[2].hand.append(deck.pop())
        elif card % 4 == 3:
            players[3].hand.append(deck.pop())
        else: return

        

deal([Jacob, Sophia, Emily, Cory], game1)  
        

print(Jacob.hand)

print(Jacob.hand.sort(key= Card.suit_rank))

I get a message that says:
AttributeError: type object ‘Card’ has no attribute ‘suit_rank’

It’s worth looking at the following for a quick introduction to how key works in sorting- Sorting HOW TO — Python 3.9.2 documentation

As for the specific error what would you if you just tried to print that attribute on its own line. Instance and class attributes are different, remember your class is an object itself.