This question already has answers here:
"Least Astonishment" and the Mutable Default Argument
(33 answers)
Closed 4 years ago.
I created a class to store some variables and dictionary. Each object will have its own dictionary. However when I created a Class in certain way, it resulted in dictionary getting shared across all objects created.
When I tweaked the init, I was able to achieve what I wanted. I want to know why dictionary got shared across different objects and when and I would that be useful.
Snippet 1: (Where dictionary gets populated and shared across all object instances)
class A(object):
def __init__(self, x, y={}):
self.x = x
self.y = y
def set_y(self,key,value):
self.y[key] = value
Snippet 2:(Where dictionary value is unique and not shared between member instances)
class A(object):
def __init__(self,x):
self.x = x
self.y = {}
def set_y(self,key,value):
self.y[key] = value
Test Driver:
l = "abcdefghijklmnopqrsqtuwxyz"
letter_list = []
node = None
for count, letter in enumerate(l,1):
if node:
letter_list.append(node)
node = A(letter)
node.set_y(letter,count)
I would like to know why dictionary got updated for all instances in first case and not for the second case
The dictionary is updated because of the way you used the default value in the __init__ constructor. In the first case, that empty dict is a single object; it is not a unique constructor for each new object. It gets evaluated when the class is defined, and the same dict object sits there for each new object instantiated. Very simply, the line
def __init__(self, x, y={}):
is executed once, when the function is defined, during the class definition.
In your second case, the initialization self.y = {} is in the body of the function, and so gets executed each time you instantiate a new object. You can see the longer explanation in this canonical posting on the topic.
Related
This question already has answers here:
"Least Astonishment" and the Mutable Default Argument
(33 answers)
Closed 2 years ago.
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?
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 = []
...
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
This question already has answers here:
"Least Astonishment" and the Mutable Default Argument
(33 answers)
Closed 3 years ago.
I am implementing a basic node object in python. Basically, I implemented a node class with the attribute f_pointers and set it to the default value []. When ever I try to change f_pointers of (lets say) node_a, I will end up changing f_pointers of node_b, which are programmed to be completely unrelated.
I have already solved the problem by instead changing the default value to None and setting up the forward_pointers in __init__. However, I would still like to know how to avoid this problem in the future and possibly learn something new about Python.
For the sake of simplicity, I removed some unnecessary parts of the code.
class Node:
def __init__(self, f_pointers = []):
self.f_pointers = f_pointers
def get_pointers(self):
return self.f_pointers
def add_pointers(self, new_pointer):
self.f_pointers.append(new_pointer)
a = Node()
b = Node()
print(a.get_pointers, b.get_pointers)
>>> [] []
a.add_pointers("a")
print(a.get_pointers, b.get_pointers)
>> ["a"] ["a"]
a.add_pointers("b")
print(a.get_pointers, b.get_pointers)
>> ["a","b"] ["a","b"]
As can be seen, a and b are completely unrelated objects (other than the fact that they are of the same type Node) but will affect each other. Why does this happen?
It's because you are referencing to the same list (the one instantiated in the __init__ default params list definition like __init__(self, f_pointers=[]). What happens is that when you say in the __init__ method code block that self.f_points = f_pointers you are basically referencing the same list every time you instantiate a new Node object.
The reasons are explained further here
What you do want to do instead is instantiate a new list for every init like:
def __init__(self, f_pointers=None):
self.f_pointers = []
You should do it like this.
class Node:
def __init__(self, f_pointers=None):
if f_pointers:
self.f_pointers = f_pointers
else:
self.f_pointers = []
def get_pointers(self):
return self.f_pointers
def add_pointers(self, new_pointer):
self.f_pointers.append(new_pointer)
a = Node()
b = Node()
print(a.get_pointers(), b.get_pointers())
a.add_pointers("a")
print(a.get_pointers(), b.get_pointers())
You get this kind of behavior because in your case a.f_pointers and b.f_pointers is the same list, which was generated, when you described your class Node.
So a.f_pointers is b.f_pointers == True in your case
This question already has answers here:
"Least Astonishment" and the Mutable Default Argument
(33 answers)
Closed 8 years ago.
I made a list containing instances of a class, each of which has an empty list as an attribute. I was trying to append one of those lists on each iteration of my script, and instead all of them got appended. The code looks like this:
class generation:
def __init__ (self, number, container=[]):
"""This is the class containing lists"""
self.n=number
self.cont=container
class hybrid_variant:
def __init__ (self, generation):
"""Instances of this class go into lists in instances of generation"""
self.gen=generation
generation_list=[]
for x in range(3):
hybrid=hybrid_variant(generation= x+1)
new_generation=True
for elem in generation_list:
if elem.n == hybrid.gen:
new_generation=False
if new_generation==True:
generation_list.append(generation(hybrid.gen))
for elem in generation_list:
if elem.n == hybrid.gen:
elem.cont.append(hybrid)
Instead of getting one element in each container attribute of all generations every generation has all of the three elements.
As described in this question mutable default parameters are stored by reference, so if all instances of your generation type will have a reference to to the same list object. As such, changing one will change it for every other instance.
To fix this, just don’t use an empty list as the default argument, but construct the empty list in the method instead:
class generation:
def __init__ (self, number, container=None):
self.n = number
self.cont = [] if container is None else container
The code looks like:
class A(object):
x = 0
y = 0
z = []
def __init__(self):
super(A, self).__init__()
if not self.y:
self.y = self.x
if not self.z:
self.z.append(self.x)
class B(A):
x = 1
class C(A):
x = 2
print C().y, C().z
print B().y, B().z
The output is
2 [2]
1 [2]
Why is z overwritten but not y? Is it because it's not a immutable type? I looked on python's documentation and didn't find an explanation.
Yes, it's because one is immutable and one isn't. Or rather, it's that you are mutating one and not the other. (What matters isn't whether the object "is mutable", what matters is whether you actually mutate it.) It's also because you're using class variables instead of instance variables (see this question).
In your class definition, you create three class variables, shared among all instances of the class. After creating an instance of your class, if you do self.x on that instance, it will not find x as an attribute on the instance, but will look it up on the class. Likewise self.z will look up on the class and find the one on the class. Note that because you made z a class variable, there is only one list that is shared among all instances of the class (including all instances of all subclasses, unless they override z).
When you do self.y = self.x, you create a new attribute, an instance attribute, on only the instance.
However, when you do self.z.append(...), you do not create a new instance variable. Rather, self.z looks up the list stored on the class, and then append mutates that list. There is no "overwriting". There is only one list, and when you do the append you are changing its contents. (Only one item is appended, because you have if not self.z, so after you append one, that is false and subsequent calls do not append anything more.)
The upshot is that reading the value of an attribute is not the same as assigning to it. When you read the value of self.x, you may be retrieving a value that is stored on the class and shared among all instances. However, if you assign a value to self.x, you are always assigning to an instance attribute; if there is already a class attribute with the same name, your instance attribute will hide that.
The issue is that x and y are immutable, while z is mutable, and you mutate it.
self.z.append() does not replace z, it just adds an item to z.
After you run C() in print C().y, C().z (which is creating two different C objects), self.z no longer evaluates to False because it is no longer empty.
If you reverse your two print lines, you'll find the output is
1 [1]
2 [1]
When Python evaluates the body of class A it instantiates a single list object and assigns it to z. Since the subclasses don't override it, and since list objects are stored by reference in Python, all three classes share the same z list, so the first one you instantiate gets to populate z and then the rest just get whatever was put there. Although you changed the contents of the list, you did not change which list z refers to.
This does not affect y because you're assigning the integer directly into the object's internal dictionary, replacing the previous value.
To fix this, create your array inside the constructor, thus assigning:
class A(object):
x = 0
y = 0
z = None
def __init__(self):
super(A, self).__init__()
if not self.y:
self.y = self.x
if not self.z:
# create a new list
z = [self.x]
class B(A):
x = 1
class C(A):
x = 2
print C().y, C().z
print B().y, B().z
This question already has answers here:
How to avoid having class data shared among instances?
(7 answers)
Closed 15 days ago.
I created a class:
class A:
aList = []
now I have function that instantiate this class and add items into the aList.
note: there are 2 items
for item in items:
a = A();
a.aList.append(item);
I find that the first A and the second A object has the same number of items in their aList.
I would expect that the first A object will have the first item in its list and the second A object will have the second item in its aList.
Can anyone explain how this happens ?
PS:
I manage to solve this problem by moving the aList inside a constructor :
def __init__(self):
self.aList = [];
but I am still curious about this behavior
You have defined the list as a class attribute.
Class attributes are shared by all instances of your class.
When you define the list in __init__ as self.aList, then the list is an attribute of your instance (self) and then everything works as you expected.
You are confusing class and object variables.
If you want objects:
class A(object):
def __init__(self):
self.aList = []
in your example aList is a class variable, you can compare it with using the 'static' keyword in other languages. The class variable of course is shared over all instances.
This happened because list is a mutable object, and it is created once only when defining the class, that is why it becomes shared when you create two instances. Eg,
class A:
a = 0 #immutable
b = [0] #mutable
a = A()
a.a = 1
a.b[0] = 1
b = A()
print b.a #print 0
print b.b[0] #print 1, affected by object "a"
Therefore, to solve the problem, we can use constructor like what you have mentioned. When we put the list in constructor, whenever the object is instantiated, the new list will also be created.
In Python, variables declared inside the class definition, instead of inside a method, are class or static variables. You may be interested in taking a look at this answer to another question.