I am currently having an issue with an object who has a dictionary as an attribute. The code consist of 3 classes, "Maps", "Nodes", and "Links." The gist is that a "Map" holds a list of "Nodes" and also a list of "Links" which connect Nodes and have a few attributes themselves. Throughout the code I am accessing a database which has attribute values that I want to load in/change on a whim, for simplicity I've replaced the database with the "external" lists below. The issue is this: Links have a dictionary called "link_data" as an attribute and when I loop through my list of links and set certain dictionary values for a specific link, it seems to change the values within the dictionaries of all of my links in the Map's link list.
Code:
class Node():
def __init__(self, number:int) -> None:
self.number = number
return
def __str__(self) -> str:
return "N{0}".format(self.number)
class Link():
def __init__(self, node1:Node, node2:Node, link_data:dict = dict()) -> None:
self.node1 = node1
self.node2 = node2
self.link_data = link_data
return
def __str__(self) -> str:
return "{0}<->{1}".format(self.node1,self.node2)
class Map():
def __init__(self, nodes:list = [], links:list = []) -> None:
self.nodes = nodes
self.links = links
return
def getNodeByNumber(self, number:int) -> Node:
return next((node for node in self.nodes if node.number == number), None)
def getLinkByNodes(self, node1:Node, node2:Node) -> Link:
match = next((link for link in self.links if link.node1 == node1 and link.node2 == node2), None)
# order of nodes doesn't matter
if (match):
return match
else:
return next((link for link in self.links if link.node1 == node2 and link.node2 == node1), None)
def setStructure(self, link_list):
self.links = []
for node1 in self.nodes:
for node2 in [node for node in self.nodes if node.number <= node1.number]:
self.links.append(Link(node1,node2))
for params in link_list:
n1 = self.getNodeByNumber(params[0])
n2 = self.getNodeByNumber(params[1])
current_link = self.getLinkByNodes(n1,n2)
print(current_link, current_link.link_data)
current_link.link_data[params[2]] = params[3]
current_link.link_data[params[4]] = params[5]
print(current_link.link_data,'\n')
return
def main():
external_node_list = [Node(number) for number in range(0,4)]
external_link_list = [[1,2,'M1',2,'M2',0],[2,3,'M1',1,'M2',1]]
mymap = Map(external_node_list)
mymap.setStructure(external_link_list)
for link in mymap.links:
print(link,link.link_data)
return
if __name__ == '__main__': main()
Output:
N2<->N1 {}
{'M1': 2, 'M2': 0}
N3<->N2 {'M1': 2, 'M2': 0}
{'M1': 1, 'M2': 1}
N0<->N0 {'M1': 1, 'M2': 1}
N1<->N0 {'M1': 1, 'M2': 1}
N1<->N1 {'M1': 1, 'M2': 1}
N2<->N0 {'M1': 1, 'M2': 1}
N2<->N1 {'M1': 1, 'M2': 1}
N2<->N2 {'M1': 1, 'M2': 1}
N3<->N0 {'M1': 1, 'M2': 1}
N3<->N1 {'M1': 1, 'M2': 1}
N3<->N2 {'M1': 1, 'M2': 1}
N3<->N3 {'M1': 1, 'M2': 1}
As you can see, I am fetching the correct link (the one between nodes 1 and 2), and setting it to the correct values (M1: 2, M2: 0), but the dictionaries of every link were set to the most recent set of values (the ones for the link between nodes 3 and 2.
It's almost as though the dictionaries are acting as class attributes rather than instance attributes. Even though (to my knowledge) they are initiated as instance attributes and I am never passing a global variable to the Link constructor when making each object (as in this question).
I have the code running as intended when I use lists or tuples instead of dictionaries, but I am really curious as to why this is happening and how to make the code function while using dictionaries, since tying strings rather than indices to my values makes a lot of sense in my project. I am still building up my intuition here, so while I am open to solutions which avoid this problem, I'd like to know exactly why it is occurring.
Edit: The post linked at the top of this question is the reason for this behavior. As others have pointed out in the comments, this is seems to be because default arguments are evaluated once and stored with the method in question. So, every instance where the argument was not given was assigned the exact same dictionary object, hence all of instances being modified when only a specific was referenced. Thank you to those who pointed this out!