Pass a list to a class python [duplicate] - python

This question already has answers here:
"Least Astonishment" and the Mutable Default Argument
(33 answers)
Closed 9 years ago.
I have this simple class:
class revs:
def __init__(self, rev, us, accs = []):
self.rev = rev
self.us = us
self.accs = accs
And i have this piece of code to asign values to the list and is inside of a loop
rev, usu = cada_l.split("|")
acct = each_l[:2].strip()
list_acct.append(acct)
and last, i create a dict, to manage a list of revs like this:
drevs = {}
cada = revs(rev, us, list_acct)
drevs[cada.rev] = cada
And it Works correctly with rev and us, but with list_acct is ever updating all the instances:
drevs['1'].rev
'1'
drevs['2'].rev
'2'
drevs['1'].us
'user1'
drevs['2'].us
'user2'
drevs['1'].accs
'["Doc1","Doc2"]'
drevs['2'].accs
'["Doc1","Doc2"]'
And if i change list_acct.clear(), the values in all the instances is clear, I'm still fairly new to Python and this confuses me.
Thanks

This looks like it's happening because you're passing the same list to every object. As a result, all the objects maintain references to the same list, and since list is mutable, it appears to change "all" of them at once.
To fix this, either pass in a new empty list each time you create a revs object, or else clone the list you're passing in:
cada = revs(rev, us, list_acct[:])
Note that if list_acct contains mutable objects, you could still get into the same problem again, but one level deeper!
If you're not passing lists to the revs objects when you create them at all (I can't tell, since you're not showing your full code!), then you have the same problem, but for a different reason: in Python, default arguments are all evaluated once, at the time of the function definition. Therefore, you can get this behavior:
r1 = revs(1, 1)
r2 = revs(2, 2)
r1.accs.append("Hi!")
print(r1.accs) # prints ['Hi!']
print(r2.accs) # prints ['Hi!']
Because the default argument for the revs constructor is always pointing to the same list. See this question for an explanation as to why, but to get around it, just use None as your default instead of [].
class revs:
def __init__(self, rev, us, accs=None):
self.rev = rev
self.us = us
if accs is None:
accs = []
self.accs = accs

Related

How to actively free/delete all associated objects when reassigning a variable in python [duplicate]

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

How to set an attirbutes list when creating new object

Hope the title was not too confusing. I've been working on program and I have created so attribut with a default list in it. The thing is I don't know how to do modification to that list when I create this object. Thank you
def __init__(self,nm,pm,adss):
self._nom = nm
self._prenom = pm
self._adresse = adss
self._bonus = [0,0,0,0]```
employer1 = Employe("nom","prenom","adresse",[0,250,50,10] #For exemple I wanted to set
#the list for that one. But im not sure if I can do it like that or if I might need to create a new function for it.
The current version would not work, since you are not passing that list as a parameter to the initialisation function. Your __init__ function would need to look like this:
def __init__(self,nm,pm,adss,bns):
self._nom = nm
self._prenom = pm
self._adresse = adss
self._bonus = bns
Then the initialiser knows that it should expect a fourth parameter to apply to the _bonus attribute.
Note that this will only set the attribute when creating the object. To change it once the object is created, you should create a setter function.
I'm not certain from your question if you want a default set for the bonus as standard. If so, see the comments on your question for details on how to do that.
You could give an immutable default value to the bonus argument, then assign to self._bonus depending on whether or not a value was passed.
It is usually good practice to make a copy of the mutable parameters passed as argument to an object.
def __init__(self, nom, prenom, adresse, bonus=None):
self._nom = nom
self._prenom = prenom
self._adresse = adresse
self._bonus = [] if bonus is None else bonus[:] # make a copy of the mutable parameter
def __init__(self, nom, prenom, adresse, bonus=None):
self._nom = nom
self._prenom = prenom
self._adresse = adresse
self._bonus = bonus if bonus else [0, 0, 0, 0]
Reasons that this answers the question:
You don't want to make a list literal (one of these: []) be the default parameter to a function/method call. This is the immutable default value bug everyone's talking about.
Setting the bonus parameter's default to None and then performing a check to see if a different value was passed is the way to go.
The cleanest way to make a check on the value of the bonus parameter is shown above.

Why are attributes in different objects connected to each other when a default argument is given? [duplicate]

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

Self variable changes when the variable it's assigned to changes [duplicate]

This question already has answers here:
How do I clone a list so that it doesn't change unexpectedly after assignment?
(24 answers)
Closed 4 years ago.
I created the class Sorter which sets the variable self.list in the __init__ equal to a given argument. I then created a function selectionSort which should copy the value of self.list into a new variable unsortedList. That worked out, but when I then change unsortedList, the self.list variable changes as well.
Here's my code:
class Sorter:
def __init__(self, list):
self.list = list
def selectionSort(self):
unsortedList = self.list
sortedList = []
indexSmallest = 0
while len(unsortedList)>0:
for i in range(len(unsortedList)):
if unsortedList[i] <= unsortedList[indexSmallest]:
indexSmallest = i
sortedList.append(unsortedList[indexSmallest])
unsortedList.pop(indexSmallest)
indexSmallest = 0
return sortedList
sorter = Sorter([2,6,1,8,5])
print(sorter.selectionSort())
I expect self.list to be the same as before calling the selectionSort() function but the result I get is an empty self.list variable.
Use either:
#1
unsortedList = self.list.copy()
Or
#2
unsortedList = self.list[:]
Or
#3
import copy
unsortedList = copy.deepcopy(self.list)
Explanation:
When you do an assignment via =, it really is referring to the same list just that now that list has 2 different names.
To circumvent this, use #1 or #2 methods -> you would require the .copy() inbuilt function or using [:].
As for #3, this is used when shallow copying isn't enough because you might have mutable objects within the list itself.
For a greater understanding on copy vs deepcopy, visit and read here
What's happening is that when you set unsortedList = self.list, Python doesn't want to copy all the values over, because that could be expensive. Instead, it just makes both variables point to the same region of memory, so when you change one it changes the other.
To make a copy, you can do unsortedList = self.list[:]
EDIT see this thread for more information.

Member variable getting shared across multiple objects- python [duplicate]

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.

Categories

Resources