pythonoopplaying-cardsclass-attributes

Class attribute defined as a length of another List Type class attribute doesn't update itself and always returns the same value


I'm writing a code for the card game 'War'. While defining a "Deck" class, I've defined an attribute "self.cards" which gives the list of all the cards currently in a deck of cards. I've also defined an attribute "self.length" which is expected to give the length of the "self.cards" attribute.

BUT, when I use the expression deck.length, it returns a fixed value, which doesn't change even when the cards in the deck ("self.cards") are removed/added. On the other hand when I use the expression len(deck.cards), it returns the desired output (that is the updated size of the deck).

The code is:

# I've used only two suits and three ranks to explain this problem

suits = ('Hearts','Spades')
ranks = ('Queen','King','Ace')

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

class Deck():
    
    def __init__(self):
        # Create an empty deck and fill it with the available cards
        self.cards = []
        for suit in suits:
            for rank in ranks:
                self.cards.append(Card(suit,rank))
        
        self.length = len(self.cards)

    def deal_top_card (self):
        self.cards.pop(0)

deck = Deck()
# Removing Three cards one after the other
deck.deal_top_card()
deck.deal_top_card()
deck.deal_top_card()
print(f"Length according to length attribute = {deck.length}")
print(f"Length of the list of cards = {len(deck.cards)}")

The output of the code above is:

Length according to length attribute = 6
Length of the list of cards = 3

Solution

  • Your problem is that length is a mere attribute. That means that it receives a value when it is defined, and keeps that value until it is assigned again.

    But Python has a concept that does what you want with properties:

    class Deck():
        
        def __init__(self):
            # Create an empty deck and fill it with the available cards
            self.cards = []
            for suit in suits:
                for rank in ranks:
                    self.cards.append(Card(suit,rank))
            
        @property
        def length(self):
            return len(self.cards)
    
        def deal_top_card (self):
            self.cards.pop(0)
    

    A property is used as a special member that actually executes a method each time it is used. You can now successfully use:

    deck = Deck()
    # Removing Three cards one after the other
    deck.deal_top_card()
    deck.deal_top_card()
    deck.deal_top_card()
    print(f"Length according to length attribute = {deck.length}")
    print(f"Length of the list of cards = {len(deck.cards)}")
    

    and get as expected:

    Length according to length attribute = 3
    Length of the list of cards = 3
    

    BTW, properties are even more powerful that that and can call other methods when assigned to or deleted. Read https://docs.python.org/3/library/functions.html#property for more...