[Budget App] Transfer Function Reference Issues


I am trying to work on the transfer function for the budget app.

Link: Budget App - Replit

def transfer(self, amount, obj):
    if self.balance >= amount:
      obj.balance +=amount
      obj.ledger.append({"amount": amount, "description": "Transfer from "+self.title})
      self.ledger.append({"amount": -amount, "description": "Transfer to "+obj.title})
      return True
    return False

I do not understand why this isn’t working. When I do obj.ledger.append, it adds the ledger object to both the obj.ledger and the self.ledger. I tried swapping it from dot notation to bracket notation ( obj["balance"] +=amount), but I get a error (similar errors when attempting to use get:

Why does obj.ledger refer to both obj.leder and self.ledger?

obj['balance'] notation won’t work here, obj.balance is the right one.

As for the seemingly adding new entry to both ledgers. In fact there’s no two ledgers, but only one, both self.ledger and obj.ledger references here the same ledger. This is because of defining ledger as a class variable, instead of instance variable. Instance variables are defined in the __init__ method. Because it’s currently class variable, and lists are mutable types when appending, the ledger variable on the instance isn’t defined, or redefined, but the original ledger gets changed.

It may appear that for balance and title variables, also defined as class variables something else is happening. As those are of immutable types, this means internally they can’t be changed, but rather are overwritten with new value.

What might make this particularly interesting (and even more confusing) is the way python resolves names when accessed by self.x. It first looks for the variables defined on the instance, if it doesn’t found it, then checks class variables, then parent classes and so on. However, when assigning to self.x, the new variable will be saved as an instance variable.

What can happen the first time line obj.balance +=amount or self.balance+=amount is executed? First balance variable is looked for in the instance variables. If not found, then class variables will be checked. There’s balance class variable, so the value of it will be read, then to that value it will be added amount. Now is the tricky part, because integers are immutable types, the original value can’t be changed to reflect added amount, but it needs to be overwritten. However, the saved value will be saved on the obj or self object, making this a newly created instance variable. Which will be later used in any further self.balance or obj.balance operation.

In case of wanting to update the actual class variable, similar notation is used, but with the class name: ClassName.variable

1 Like

I tried updating the Init to make it correct so they would be class varaibles. Using self instead just produces issues of “Category” is not defined when accessing in the transfer function when using Category. variable.

  def __init__(self, catagoryName):
    Category.title = catagoryName
    Category.ledger = []
    Category.balance = 0.00

However, now I get similar references issues where both obj and category refer to the same thing:

[{'amount': 50, 'description': 'Transfer from Clothing'}]
[{'amount': 50, 'description': 'Transfer from Clothing'}]

Yeah, that’s the same effect, but in a slightly different situation. I’d suggest to using instance variables here, that way ledger won’t be shared between multiple Category instances, but each Category will have it own ledger.

where can i look up information about using obj. Is there a video about it?
i just tried to add obj. in front of my code and it works.
but i don’t really understand why