Or S
Or S

Reputation: 13

class attribute doesnt work properly (python)

While trying to come up with a Hand class for a card game I encountered a strange behavior from an attribute

if I try to set self.number as seen below it wont show the proper output but if I make the same argument through a function total() it works properly

my question is: why does the attribute self.number not getting the value of len(self.cards)?

class Hand (object):

def __init__(self,number=0,cards=[]):

    self.cards=cards

    self.number=len(self.cards)

def total(self):
    return len(self.cards)

hand=Hand()
hand.cards.append(9)

print hand.cards
print len(hand.cards)
print hand.number
print hand.total()

output:
[9]
1
0    #I want this to be equal to 1
1

Upvotes: 1

Views: 40

Answers (2)

Jared Goguen
Jared Goguen

Reputation: 9010

The attribute self.number is set at instantiation, use a property instead.

class Hand (object):
    def __init__(self, cards=None):
        self.cards = cards or []

    @property
    def number(self):
        return len(self.cards)

    def total(self):
        return len(self.cards)

Upvotes: 4

Pedro Castilho
Pedro Castilho

Reputation: 10512

Setting an instance variable to an expression does not create a binding between the inputs of that expression and the value of the instance variable. In other terms, setting self.number to len(self.cards) does not mean that self.number will get updated whenever you update self.cards.

Your program is functioning properly: When you create your object, len(self.cards) is 0, so self.number gets set to 0. When you change hand.cards, there are no statements changing self.number, so it stays 0.

The proper way to make your self.number attribute update is to use a getter-setter pair in order to make sure that self.number changes whenever self.cards changes.

The Pythonic way to create a getter-setter pair is to use the @property decorator.

In your case, you could do it like this:

class Hand(object):
    def __init__(self, number = 0, cards = None):
        self.cards = cards or []

    @property
    def number(self):
        return len(self.cards)

This way, even though it looks to anyone uses your class that they are reading an attribute, what actually happens under the hood is that the number() method gets called and correctly computes the current length of self.cards.

Upvotes: 2

Related Questions