Python class method appends item several times in .ipynb environment - python

Imagine we have a Class of foo with a certain structure defined in one cell:
class foo:
def __init__(self, lst = []):
self.lst = lst
def appendlst(self, item):
self.lst.append(item)
def display(self):
for item in self.lst:
print(item)
And in the next cell we have an object initialization with method calls:
dummy = foo()
dummy.appendlst(0)
dummy.appendlst(5)
dummy.display()
When I run the cell one time, output is as expected to be:
0
5
But when I run the cell twice, I expect the object to be reinitialized and to print the previous output once, instead I have:
0
5
0
5
On the opposite, if I reinitialize in the cell in the following manner:
dummy = foo()
dummy.appendlst(0)
dummy.appendlst(5)
dummy = foo()
dummy.appendlst(0)
dummy.appendlst(5)
dummy.display()
Output is as expected to be:
0
5
Also, changing the self.lst = lst to be self.lst = [], resolves the first case, however, making class incapable for initializing with anything, except the null list.
What is the source of that strange behavior with list being not null, when running the cell twice?

This is because you have used the anti-pattern of using a mutable default value as an argument.
The fix is
def __init__(self, lst = None):
if lst is None:
lst = []
self.lst = lst
In short, by setting the default value to [] you are creating a new list and setting the default value to a reference to that list. So every time you call that init function, instead of creating a new list as you were expecting, instead you are getting a reference to a list that is now shared between all your foo objects. (Incidentally by pep8 naming conventions you should have called it Foo).
This is compounded by Jupyter's design which encourages bad practices like running cells out of sequence leading to unreproducible program states. i.e. be very careful about running or rerunning any cell in Jupyter without running all the cells in order from the start.

Related

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

Python return value prints only one value from the object instead of all the values

I have a Python file with two class A and B, and I am inheriting temp from class A to B. In my class A function temp, I am getting the id value from an XML file which has values like this,
[1,2,3,4,5]
My Class A with temp function
class A:
def temp(self):
id = []
for child in root:
temp_id=child.get('id')
id.append(temp_id)
return id
and I am getting the function value in class B,
class B (A):
fun=A
value = fun.temp()
for x in value:
return x
While printing the return value instead of printing all the values, I am getting only the first value
[1,1,1,1,1]
Can some one please let me know on how to solve this, thanks.
Standard functions can only return a single value. After that, execution continues from where that function was called. So, you're trying to loop through all the values, but return upon seeing the first value, then the function exits and the loop is broken.
In your case, you could simply return fun.temp(). Or, if you want to process each value, then return the new list, you could run something like:
new_list = []
value = fun.temp()
for x in value:
# do something with x here
new_list.append(x)
return new_list
Also, it appears you may be a bit confused about how inheritance works. DigitalOcean has a great article on it, if you want to take a look.

Python default params confusion

I just started learning Python and I'm confused about this example:
def append_to(element, to=None):
if to is None:
to = []
to.append(element)
return to
If to was initialized once, wouldn't to not be None the 2nd time it's called? I know the code above works but can't wrap my head around this "initialized once" description.
def append_to(element, to=None):
to = ...
to here becomes a local variable and is assigned to a list, and is deallocated if you don't assign return value to another variable.
If you want to staying alive for subsequent calls to append_to you should do:
def append_to(element, to=[]):
to.append(element)
return to
Demo:
>>> lst = append_to(5)
>>> append_to(6)
>>> append_to(7)
>>> print lst
[5, 6, 7]
If "to" was initialized once, wouldn't "to" won't be "None" the 2nd time it's called?
to would become None if you don't pass a value for it: append_to(1) and only when to is None will your code rebind the local name to to a newly created list inside the body of your function: to = [].
The default values of functions are assigned only once, that's whatever you assign as a default value, that object will be used for every call you make to the function and will not change, typically the same reference of the default value will be used for every call you make to the function. This matters when you assign mutables as defaults:
l = []
def defArgs(d=l) # default arguments, same default list for every call
d.append(1)
return d
defArgs() is l # Object identity test: True
Run the above function multiple times and you will observe the list growing with more elements because you get only one single copy of argument defaults for every function shared by every function call. But notice here:
def localVars(d=None):
if d is None:
d = [] # Different list for every call when d is None
d = [] is executed every time you call localVars; when the function finishes its job, every local variable is garbage-collected when reference count drops to 0, but not the default values of arguments, they live after the execution of the function and are not typically garbage-collected after the execution of the function.
In Python there is no declaration and initialization stage when you use a variable. Instead, all assignment is a definition where you bind an instance to the variable name.
The interpreter bind the instance to the default value when instantiate the function. When the default value is a mutable object and you only change its state by its methods than the value will be "shared" between calls.

When does `def` run in python? When will function object be initialized?

Say we have a function in a file, something like:
..... other fucntions
def add_to_list(l = []):
l.append("foo")
..... other functions
When will def be called? What exactly does def do?
[] creates a list and, for efficiency reasons, when a list is passed, it isn't duplicated or passed by value; it is sent by reference. A similar thing is happening with this. So, firstly all def's are initialized by Python. Then your code is run. However, the initializer has already created the list.
>>> id(add_to_list())
12516768
>>> id(add_to_list())
12516768
>>> id(add_to_list())
12516768
Excerpt from http://effbot.org/zone/default-values.htm
Notice how they have the same id? That's because it's returning the same instance of list every time. Thus, l = [] isn't creating a new instance at run time. So, we keep on appending to the same instance.
That is why we don't experience this behavior with ints, floats, and etc.
Consider your code:
def add_to_list(l = []):
l.append("foo")
print(l)
add_to_list()
add_to_list()
Then it's output:
['foo']
['foo', 'foo']
Now consider this similar function:
def add_to_list():
l = []
l.append("foo")
print(l)
add_to_list()
add_to_list()
The output will be:
['foo']
['foo']
That's because the l = [] is run after the constructors are initialized (in live time). When you consider the first code, you will see that the constructor is ran first, then the code executes live time.
def is executed whenever you hit it in parsing the source file. It defines the function. This means that the body of the function is assigned to the name, the parameters are included in the "signature" (calling format), etc. IN other words, the function is now ready to call from anywhere below that point, within the scope of the containing program.
Since l is a list, it's bound to whatever you pass into the function call. If you pass in a list, then it's bound by reference to that list, a mutable object.
As to your specific case, there's really no use case that will make sense: since you have to pass an object with an append method, anything you supply will have a value: it will never use the default value. The only way to get that default value into play is to call with empty parentheses, in which case there's no way to get the value back to the calling program (since you return nothing).
Thus, when you call the routine a second time, it appears that you're using the same list you passed the first time. That list already has the "foo" element you added that first time. Here's some test code and the output to illustrate the effect:
def add_to_list(l = []):
l.append("foo")
print "l =", l
empty = []
add_to_list(empty)
print "empty #1", empty
add_to_list(empty)
print "empty #2", empty
add_to_list()
Output:
l = ['foo']
empty #1 ['foo']
l = ['foo', 'foo']
empty #2 ['foo', 'foo']
l = ['foo']
Does that clarify things?

Why does .append() not work on this list?

I have an object scene which is an instance of class Scene and has a list children which returns:
[<pythreejs.pythreejs.Mesh object at 0x000000002E836A90>, <pythreejs.pythreejs.SurfaceGrid object at 0x000000002DBF9F60>, <pythreejs.pythreejs.Mesh object at 0x000000002E8362E8>, <pythreejs.pythreejs.AmbientLight object at 0x000000002E8366D8>, <pythreejs.pythreejs.DirectionalLight object at 0x000000002E836630>]
If i want to update this list with a point which has type:
<class 'pythreejs.pythreejs.Mesh'>
I need to execute:
scene.children = list(scene.children) + [point]
Usually, I would execute:
scene.children.append(point)
However, while these two approaches both append point, only the first actually updates the list and produce the expected output (that is; voxels on a grid). Why?
The full code can be found here.
I am guessing your issue is due to children being a property (or other descriptor) rather than a simple attribute of the Scene instance you're interacting with. You can get a list of the children, or assign a new list of children to the attribute, but the lists you're dealing with are not really how the class keeps track of its children internally. If you modify the list you get from scene.children, the modifications are not reflected in the class.
One way to test this would be to save the list from scene.children several times in different variables and see if they are all the same list or not. Try:
a = scene.children
b = scene.children
c = scene.children
print(id(a), id(b), id(c))
I suspect you'll get different ids for each list.
Here's a class that demonstrates the same issue you are seeing:
class Test(object):
def __init__(self, values=()):
self._values = list(values)
#property
def values(self):
return list(self._values)
#values.setter
def values(self, new_values):
self._values = list(new_values)
Each time you check the values property, you'll get a new (copied) list.
I don't think there's a fix that is fundamentally different than what you've found to work. You might streamline things a little by by using:
scene.children += [point]
Because of how the += operator in Python works, this extends the list and then reassigns it back to scene.children (a += b is equivalent to a = a.__iadd__(b) if the __iadd__ method exists).
Per this issue, it turns out this is a traitlets issue. Modifying elements of self.children does not trigger an event notification unless a new list is defined.

Categories

Resources