1

It seems that python does not delete associated objects when a variable is reassigned. Have a look at the following code:

from dataclasses import dataclass

@dataclass
class Transaction(object):
    amount : int
    new_balance : int
    description : str

class Account(object):
    def __init__(self, acc_name, balance, transactions = []):
        self._acc_name = acc_name
        self._balance = balance
        self._transactions = transactions # list of Transaction objects

    def add_income(self, amount, description = "income"):
        self._balance += amount
        self.transactions.append(Transaction(
            amount=amount,
            new_balance=self._balance,
            description=description
            ))
        
    @property
    def transactions(self):
        return self._transactions



acc = Account("User",100)
acc.add_income(100)
print(acc.transactions)

acc = None

acc = Account("User",100)
print(acc.transactions)

The output is

[Transaction(amount=100, new_balance=200, description='income')]
[Transaction(amount=100, new_balance=200, description='income')]

So we can see even though I reassigned the variable the Transaction object is still alive. In encountered this behavior, when I wanted to create a fresh instance for my tests in the setUp method from unittest. Why is it like this? Is there a possibility to delete the "child" object when the "parent" object is deleted? What is the best practice in this situation?

Mohsen Alyafei
  • 4,765
  • 3
  • 30
  • 42
CRaNkXD
  • 13
  • 2
  • 1
    Have a look at mutable default arguments https://docs.python-guide.org/writing/gotchas/#mutable-default-arguments – Tom Wojcik Jul 05 '20 at 12:38

2 Answers2

4

The problem is with the default value of transactions; it is shared between all instances of the class.

The usual idiom is

def __init__(..., transactions=None) :
    if transactions is None:
        transactions = []
   ...
AKX
  • 152,115
  • 15
  • 115
  • 172
2

List is a mutable type. Using mutable types as default arguments is not recommended as this leads to this behavior. This is because default arguments are evaluated only once when the method is created. Each call does not cause re-evaluation and creation of a fresh list instance. This seems unusual but as soon as you begin to think of the function as an "object" whose value is initialized at creation, this makes sense.

The same list is reused everytime you do not provide the transactions argument to the constructor. In other words, this default value is shared between subsequent function calls. The recommended approach when using mutable data types as default arguments is to set the default value to None instead and check it and assign the desired value inside the function.

def __init__(self, acc_name, balance, transactions=None):
        if transactions is None: transactions = []
        self._acc_name = acc_name
        self._balance = balance
        self._transactions = transactions # list of Transaction objects

Amal K
  • 4,359
  • 2
  • 22
  • 44
  • 1
    Thank you! Didn't encountered this behavior before. And didn't know functions are initialized just once. Which actually makes perfect sense. – CRaNkXD Jul 05 '20 at 13:15