0

I (accidentally) wrote the following code:

#!/usr/bin/python3.4

class Account:

    balance = 1000

    def withdraw(self, amount):
        print('Withdrawing')
        if(amount > self.balance):
            print("You cannot withdraw that amount!")
        else:
            self.balance -= amount

    def checkBalance(self):
        if (self.balance == 0):
            print("Your account is empty.")
        else:
            print("You still have ", str(self.balance) + "€ euros in your account!")


def Main():
    account1 = Account()
    account2 = Account()

    account1.withdraw(100)
    account1.withdraw(300)

    account1.checkBalance()
    account2.checkBalance()


if __name__ == "__main__":
    Main()

output:

Withdrawing
Withdrawing
You still have 600€ euros in your account!
You still have 1000€ euros in your account!

My questions:

1) why the balance variable does not behave as a class (static) variable although it has been declared as such?

2) even if the usage of the self keyword when accessing the balance variable from within the methods is preventing the static behavior, at which point is an instance copy of the class variable balance made? (at least that is what I am inferring, that each object gets its own copy of the -what is supposed to be a class variable- balance)

3) by trial and error, I discovered that a way to get the (expected?) behavior is to annotate the methods with @classmethod. Is this s.th like in Java that prevents access of static variables from non-static methods?

I am aware of this and this posts but IMHO they do not address the above implications.

Community
  • 1
  • 1
pkaramol
  • 16,451
  • 43
  • 149
  • 324

1 Answers1

4

The trick is here:

self.balance -= amount

This actually translates to:

self.balance = self.balance - amount

Now, the complicated thing is that self.balance is not actually the same thing on either side of that assignment. The RHS is evaluated first: Python tries to find an instance variable, fails, and falls back to the class variable. But now it does the LHS, and here it simply does what it normally does: assigns directly to an instance variable called balance.

The point is that the LHS is unaware that the original value came from a class variable; it simply assigns where it is told, to self.balance, which is an instance var.

Of course from then on, the instance var exists, so any further reads of self.balance will get that rather than the original class variable: the class variable exists unchanged, but is shadowed by the instance variable.

When you define the method as a classmethod, the first parameter is no longer the instance (self) but the class itself; for this reason it is customary to call that argument cls. So you read and write directly to the class variable.

Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
  • Exactly what I would have said, if I could have expressed it so elegantly. If `self.balance` were replaced with `Account.balance` then it would behave as the OP expected. – mhawke Mar 27 '15 at 13:32
  • perhaps mention that the class variable still exists, it's just that it's not being refered to in the `self.checkBalance` method. – will Mar 27 '15 at 13:40
  • So when python fails to find an instance variable `self.x`, it falls back to looking up for a class variable `x` ? In that case that actually takes place in the comparison `if amount > self.balance`. which precedes the statement with the decrement operator. Also: is the `@classmethod` annotation preventing the creation of instance variable DESPITE the existence of the `self.` ? Is the annotation instructing the interpreter to ignore anything before (including) the `.` ? – pkaramol Mar 27 '15 at 13:44
  • Well, it does it everywhere, but the interesting stuff is what I describe happening in the assignment statement. And no, the point is that what you're calling `self` in the classmethod is not really self but cls: it's the class, not an instance. Don't forget `self` and `cls` are just conventions, you can use whatever names you like, but it's best to be clear that in the case of a classmethod the first param is the class object. – Daniel Roseman Mar 27 '15 at 13:50