In Python, why does .append give different results than using +? [duplicate] - python

This question already has answers here:
Why does using `arg=None` fix Python's mutable default argument issue?
(5 answers)
Closed 3 years ago.
I want to implement a very simple tree data structure in Python.
I want to make it so that everytime I add a new node and I specify its parent, then it is automatically added to the children attribute of its parent.
I have two different ways to do it, one works and one doesn't but I don't understand why.
class Node():
def __init__(self, value, parent = None, children = []):
self.value = value #This is for us to see the name
self.parent = parent #Parent node
self.children = children #List of child nodes
#Set this Node as a children of its own parent
if not parent == None:
#self.parent.children.append(self) <--- wrong code
self.parent.children = self.parent.children + [self]
def __repr__(self):
return str(self.value)
tree = Node("tree")
branch1 = Node("branch1", parent = tree)
branch2 = Node("branch2", parent = tree)
leaf = Node("leaf", parent = branch1)
Here is what I get with the code as is and what I would get if I replace the last line of __init__ with the commentated line.
print(tree.children)
#[branch1, branch2] <--- expected
#[branch1, branch2, leaf] <--- with wrong code
print(branch1.children)
#[leaf] <--- expected
#[branch1, branch2, leaf] <--- with wrong code
Using the .append method adds the node not only to the list children of its parent but to everybody. Even if I define a new Node("other") completely detached from the others. Why is that?

The problem is in the use of mutable default value:
def __init__(self, value, parent = None, children = []):
The empty list [] gets created only once, when the function is defined, and all invocations share the same list! This is why append to one list of children modifies all of them - because they all are one and the same list object. When you use + to append to the list, you work around the above bug because you re-create the list every time, thus unsharing the children objects.
The correct solution is to replace children=[] with something like:
def __init__(self, value, parent=None, children=None):
if children is None:
children = []
That will guarantee the creation of a new list for children, and then append and + should have the same result.

Related

Printing linked-list in python

In my task first I need to make single linked-list from array.
My code:
class Node:
def __init__(self,data):
self.data = data
self.next = next
class Lista:
def __init__(self, lista=None)
self.head = None
def ispis(self):
printval = self.head
while printval .next is not None:
print(printval.next.data)
printval = printval.next
if __name__ == '__main__'
L = Lista ([2, "python", 3, "bill", 4, "java"])
ispis(L)
With function ispis I need to print elements of linked-list. But it says name "ispis" is not defined. Cannot change ispis(L) !
EDIT: removed next from and ispis(self) is moved outside Lista class
while printvla.next is not None:
EDIT2:
It shows that L is empty so thats why it won't print anything. Should I add elements to class Node ?
ispis is a method in a class. But you are calling the function as if it is a normal function outside the class.
Atleast you have created the object correctly. Below would be the correct way of calling the method inside the class.
L.ispis()
This question sounds suspiciously like a homework assignment. If the instructor is trying to teach you how to create linked lists, you need to go back to what you need to do:
A node when set up for the first time only needs the data. Typically the next pointer/value would be set to None (meaning no next member).
Your __init__ method for your Lista class needs to do something with its argument.
I believe if you need to use your ispls function to operate on a class, then the function probably isn't supposed to be a member of Lista.
I think your ispls loop shouldn't be testing its .next member. This would fail if you had a None to begin with. You should be testing the current instance rather than its next. That way, when you move on to the next node, if it's None, it gets out of the loop.
Be careful with the keyword next. I would avoid using it as a class attribute. Also the literal next would just give you the built-in command.
At the bare minimum, you would want to iterate over the lista argument in __init__, creating a Node for each one, saving the previous node for the next operation.
if lista is None:
self.head = None
return
prev = None
for data in lista:
node = Node(data)
if prev is None:
self.head = node
else:
prev.next = node
prev = node
But again, I believe that is what the instructor wanted you to figure out. Hope this helped.
--B

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

Recursive loop not having expected behavior

So I create a tree in python. I am trying to change some value of every child of the root node. But, every node in my tree is not being hit.
class Node(object):
def __init__(self, value, priority):
self.parent = None
self.children = []
self.value = value
self.priority = priority
def add_child(self, obj):
self.children.insert(obj)
obj.parent = self
def getChildren(self):
return self.children.getAll()
tom = Node("DD",1)
tom.add_child(Node("a",0.3))
tom.add_child (Node("b", 0.6))
tom.getChildren()[0].add_child(Node("c",1))
tom.getChildren()[1].add_child(Node("d",1))
#print(tom.popHighestValue().value)
def getAll(currentNode):
print(currentNode.value)
if(currentNode.getChildren != []):
for sibling in currentNode.getChildren():
sibling.priority = 1
return getAll(sibling)
The tree should look like:
DD
/\
a b
/
c
But only DD->a->c are being hit. I thought the for loop state would be saved and continued after DD -> c was traversed.
The goal is that every node in the tree is hit. And the priority value is set to 1.
A return statement always exits the current function. If you're in a loop when you do this, the rest of the loop is never executed.
If you need to return all the values of the recursive calls, you need to collect them in a list during the loop, then return that list after the loop is done.
But in this case there doesn't seem to be anything you need to return. This function is just for setting an attribute, there's nothing being extracted. So just make the recursive call without returning.
def getAll(currentNode):
print(currentNode.value)
for sibling in currentNode.getChildren():
sibling.priority = 1
getAll(sibling)
BTW, this won't set the priority of the root node, since it only sets the priority of children. If you want to include the root node, it should be:
def getAll(currentNode):
print(currentNode.value)
currentNode.priority = 1
for sibling in currentNode.getChildren():
getAll(sibling)
Also, you shouldn't call getAll() in the getChildren() method. It just return self.children, not self.children.getAll().
If you remove the return before calling getAll() and place it outside the enclosing for loop, that would fix your problem.
In your code, you are unable to process all the children because right after your first iteration you call getAll() with the return statement. So, all the other siblings except first are/will not be explored at every depth.

Python creating static objects which are shared amongst all class objects

I have a class node something like this. It's a typical node object for a graph.
class Node(object):
def __init__(self, data, edges = []):
super(Node, self).__init__()
self.data = data
self.edges = edges
self.visited = False
def addEdge(self, *args):
print(self)
self.edges.extend(args)
print(self.edges)
I create two objects like this -
one = Node(1)
two = Node(2)
Next I add a pointer of two to one using the addEdge method defined above -
one.addEdge(two)
Now comes the surprising bit. When I check the values of one.edges and two.edges I get this -
one.edges
[<main.Node object at 0x109ed3e50>]
two.edges
[<main.Node object at 0x109ed3e50>].
If you see both the objects have gotten the value. I'm quite puzzled at this and have no idea why this is happening. Is this how python behaves? If so can you explain this behaviour?
You need to be careful when using an array literal as a default value because you don't get a new array with each instance — you get a reference to the same one. In your example you will see:
>> one.edges is two.edges
True
You need to do something to make sure you get a new array each time. One things you can do is:
self.edges = list(edges)
Another option is:
def __init__(self, data, edges = None):
if edges is None:
self.edges = []
else:
self.edges = edges
But edges is still mutable so it may also lead to subtle bugs if the caller is not expecting it to be changed.

Append to the same list

I am from a solid C/C++ background, so please bear with me if my question is silly.
Here I have two classes that declared to encapsulate data,
class Node:
Term = ""
TermInfo = []
def __init__(self,S,Info):
self.Term = S
self.TermInfo.append(Info)
class TermInfo:
DocID = 0
Freq = 0
def __init__(self,ID,F):
self.DocID = ID
self.Freq = F
and I was trying to manipulate them this way
Info = TermInfo(ID,0)
node = Node(j,Info)
Dict[j] = node
Basically I was trying to construct a dictionary that contains nodes that are made of a string "Term" and a list of "Terminfo", I was expecting that each node has its own copy. However, after I called the three lines twice in a row
Info = TermInfo(ID,0)
node = Node(j,Info)
Dict[j] = node
Info = TermInfo(ID,0)
node = Node(j,Info)
Dict[j] = node
I was surprised to see that the two lists of "TermInfo" were pointing to the same memory address and the second append() also changed the first list,So how do I make sure each node has its own copy instead of pointing to the same address? Thanks
Short answer:
create a new list in __init__.
def __init__(self,S,Info):
self.Term = S
self.TermInfo = [Info]
Long answer:
This has to do with how python looks up attributes. When you do: instance.attribute, python first looks for the attribute on the instance. If it's not found there, it looks for the attribute on the class. Here you have a list attribute on the class and you keep on appending to that attribute via the instance. So when you have the line:
self.TermInfo.append(whatever)
Python first looks at self. however, self doesn't have a TermInfo attribute, so then python looks at self.__class__ (Node in this case) for the TermInfo attribute -- and it finds it so it appends to the Node.TermInfo list.
Only attributes assigned in __init__ and after object construction are instance attributes. Attributes assigned in the class definition are class attributes, similar to static class members in other languages. So if you want to have a per-instance attribute, assign its initial value in __init__.

Categories

Resources