Python dictionary is somehow updating - python

I've tried my best to figure this out, but I can't for the life of me. I have a dictionary with many different values in it, including another dictionary. Before setting the values for the dictionary within the dictionary, I try to set the values equal to a "blank" dictionary such that in subsequent steps I can update it.
The short story is: I have two lines that somehow are changing a dictionary that I wouldn't expect. Given some dicts:
blankresiduedict = {stuff:[stuff], stuff:[stuff]}; blankidentifiers = {stuff:stuff, stuff:stuff}
the lines
self.pdb_chain_dict['1MH9_B'] = (blankresiduedict.copy(),blankidentifiers.copy())
self.pdb_chain_dict['1MH9_B'][0][(stuff)][0] = residuedict[('A','D','41')]
are somehow changing the values of blankresiduedict to be equal to residuedict.
Any idea how this is happening? There is literally no other reference to blankresiduedict in that section of code, and when I look at the output, blankresiduedict starts out accurate and then with each loop keeps changing value to equal whatever residuedict was for that loop.
(Below is a more detailed description)
This is a small part of a very large project, so some of this may really be hard to represent in a compact form. I'll do my best to eliminate the unnecessary stuff. This is a method within a class that I am trying to use to update the dictionary for the class instance.
blankresiduedict = {}
blankidentifiers = {}
self.allowmultiples = True
self.ancestorline = [
'1MH9',
'A', 'D', '41',
'A', 'D', '43',
'A', 'T', '130',
#etc...
]
self.no_key_residues = 6
self.pdb_chain_dict = {
'1MH9_B': (
{
('A','D','41'): [('B','D','41')],
('A','D','43'): [('B','D','43')],
('A','T','130'): [('B','T','130')]
},
#{identifiers dictionary}
),
'1MH9_C': (
#{etc},{etc}
),
# etc...
}
for i in range(1, (3*self.no_key_residues)+1, 3): # Using this loop structure allows a variable number of key residues to be given
if not self.allowmultiples:
raise Exception("Do some stuff here")
else:
blankresiduedict[(self.ancestorline[i],self.ancestorline[i+1],self.ancestorline[i+2])] = [('-','-','-')]
blankidentifiers = {'EC Num':'-','Sprot':'-','Class':'-','Keywords':'-','Title':'-','SeqRepr':'-'}
### Begin some loop structure, where for every loop, the following is basically happening
residuedict = {
('A','D','41'): ('B','D','10'),
('A','D','43'): ('B','D','12')
} #in actuality this value would change for every loop, but just showing what a typical loop would look like
self.pdb_chain_dict['1MH9_B'] = (blankresiduedict.copy(),blankidentifiers.copy())
self.pdb_chain_dict['1MH9_B'][0][('A','D','41')][0] = residuedict[('A','D','41')]
What should happen here is that the value in the pdb_chain_dict is set to the the tuple of two blank dictionaries ({residuedict},{identifiers}) I'm mostly leaving the identifier dictionary alone in this example because it has the exact same problem. However, what I'm finding is that the blankresiduedict is actually changing. And, after doing a lot of testing, the line where it is changing is self.pdb_chain_dict['1MH9_B'][0][('A,'D','41')][0] = residuedict[('A','D','41')].
This makes no sense to me...blankresiduedict is not even involved, yet somehow it's value is being changed in that step.

That's because a copy of a dictionary is not a deep copy, and your dict values are lists, which are mutable. Here's a minimal example that reproduces your issue:
d1 = {"foo": [1, 2, 3]}
d2 = d1.copy()
# Add a new element to d2 to show that the copy worked
d2["bar"] = []
# The two dicts are different.
print d1
print d2
# However, the list wasn't copied
# it's the same object that shows up in 2 different dicts
print d1["foo"] is d2["foo"]
# So that's what happens in your code: you're mutating the list.
d1["foo"].append(5)
print d2["foo"]

Related

Unable to store values in an array from for loop in python

First I tried directly storing values from a list having the name 'data' in an array variable 'c' using loop but 'none' got printed
for i in data:
print(i['name'])
c=i['name']
Here print(i['name']) perfectly worked and output appeared
This is the working ouput
Then I printed c in order to print the values generated using loop. The ouput came as none.
print(c)
Then I tried another way by storing the values and making the array iterable at the same time using for loop. An error occurred which I was unable to resolve.
for i in data:
b[c]=i['name']
c=c+1
The error apeared is as follow-
I have tried two ways, if there is any other way please help me out as I am new to python.
It looks like the variable 'data' is a dictionary.
If you want to add each name from that dictionary to a list:
# create a new list variable
names = []
for i in data:
name = i['name']
print(name)
# add the name to the list
names.append(name)
# output the new list
print(names)
Assuming your data object here is a list like [{"name": "Mr. Green", ...}, {"name": "Mr. Blue", ...}].
If your goal is to end up with c == ["Mr. Green", "Mr. Blue"], then you're looking for something like:
c = []
for i in data:
c.append(i['name'])
print(c)
or you can accomplish tasks like these using list comprehensions like:
c = [i['name'] for i in data]
print(c)
The first code example you posted is iterating through the items in data and reassigning the value of c to each item's name key - not adding them to a list("array"). Without knowing more about the code you ran to produce the screenshot and/or the contents of data, it's hard to say why you're seeing print(c) produce None. I'd guess the last item in data is something like {"name": None, ...} which if it's coming from JSON is possible if the value is null. Small note: I'd generally use .get("name") here instead so that your program doesn't blow up if an item is missing a "name" key entirely.
For your second code example, the error is different but I think falls along a similar logical fallacy which is that lists in python function differently from primitives(things like numbers and strings). For the interpreter to know that b or c are supposed to be lists("arrays"), they need to be instantiated differently and they have their own set of syntax/methods for mutation. For example, like arrays in other languages, lists are indexed by position so doing b[c] = <something> will only work if c is an integer. So something similar to your second example that would also produce a list of names like my above would be:
b = [None] * len(data)
c = 0
for i in data:
b[c]=i['name']
c=c+1
Note that if you only initialize b = [], you get an IndexError: list assignment index out of range on the initial assignment of b[0] = "some name" because the list is of size 0.
Add
b = []
above your first line of code. As the error is saying that you have not (and correctly so) defined the list to append.
I personally would use list comprehension here
b = [obj['name'] for obj in data]
where obj is i as you have defined it.

Python 3.8+ Tuple to variable index?

I have a variable, jdata, that holds data read from a JSON data file. It consists of many levels of dictionaries and lists of dictionaries. I have a search routine that returns a tuple containing path-like information to the element I want to access. I'm struggling to turn the tuple into a variable index. For example, the search routine may return ('name', 5, 'pic', 3). So I want to access jdata['name'][5]['pic'][3]. The number of levels down into the data can change for each search, so the tuple length is variable.
Addendum:
for everyone asking for code and what I've done:
I don't have code to share because I don't know how to do it and that's why I'm asking here. My first thought was to try and create the text for accessing the variable, for the example above,
"x = jdata['name'][5]['pic'][3]"
and then looking for a python way of executing that line of code. I figured there has to be a more elegant solution.
I thought the description of tuple to variable access was pretty straight forward, but here is an expanded version of my problem.
jdata = { 'thing1': 1,
'name': [
{},
{},
{},
{},
{},
{ 'thing11': 1,
'pic': [ 'LocationOfPicA',
'LocationOfPicB',
'LocationOfPicC',
'LocationOfPicD',
'LocationOfPicE'],
'thing12: 2},
{},
{} ],
'thing2': 2}
I searched for 'PicD' and my search code returns: ('name', 5, 'pic', 3)
Now I want to do some stuff, for example, accessing the value 'LocationOfPicD', copy the file located there to some other place, and update the value of 'LocationOfPicD' to the new value. All of this I can code. I just need to be able to turn the tuple into an accessible variable.
Edit: I was just reading about mutability in python. Instead of generating a path to an element in the dictionary, I think I can just assign that element value to a variable (x, for example) that gets passed back up the recursion chain of the initial search. From my understanding, I can change x and that also changes the element within the jdata variable. If that doesn't work, I can resort to using the eval() command on my generated text statement using the tuple as originally planned.
If I understand the problem correctly, you just need to avoid getting the lowest level item by value. So, you could do something like
indexes = ('name', 5, 'pic', 3)
x = jdata
for index in indexes[:-1]:
x = x[index]
x[indexes[-1]] = <new_value_here>
Easy and quick recursive implementation.
def get_d(d, tup, ind=0):
if ind == len(tup) - 1: # last item just return value
return d[tup[ind]]
return get_d(d[tup[ind]], tup, ind + 1) # else keep getting sub-item
# input input datastructure (could be dict, list, or gettable item) and tuple of items to recursively get
value = get_d(jdata, ('name', 5, 'pic', 3))
Note: this implementation is super basic and has no error handling. It's just here to give you an idea on how it could be done.

Receiving multiple function outputs in one line

Suppose I have multiple functions:
type = {'a', 'b', ..., 'z'}
f={}
f['a'] = some func_a...
f['b'] = some func_b...
...
f['z'] = some func_z...
Now I want to get the outputs of them
output = {}
for t in type:
output[t] = f[t](input)
I wonder if there is any way that we can do this in one line using a loop in a different way, like
[output[t] for t in type] = [f[t](input) for t in type]
Of course, this does not work. So would there be any valid way?
You want a dictionary comprehension. It works just like a list comprehension, but instead of a single expression to form the values, you get to provide two expressions to generate both a key and a value:
output = {t: f[t](input) for t in type}
The dict comprehension produces a new dictionary object; there is no need or use for an initial output = {} line.
I'd just iterate over the items of f, as it already has the keys we need:
output = {t: func(input) for t, func in f.items()}
As a side note, instead of using separate assignments for all your f functions, just use a single dictionary definition:
f = {
'a': some_func_a,
'b': some_func_b,
# ...
'z': some_func_z,
}
type is not a great name for a variable, either, as that masks the built-in function you may sometimes want to use. You don't need to create that set separately, as iteration over f would give you the same keys, or you can use set(f) to create a set copy, or f.keys(), to get a dictionary view object over the keys of f, which acts just like a set but is 'live' in that changes to f are reflected in it.

Adding multiple elements to list in dictionary

So I made this method to set parameters from a text file:
def set_params(self, params, previous_response=None):
if len(params) > 0:
param_value_list = params.split('&')
self.params = {
param_value.split()[0]: json.loads(previous_response.decode())[param_value.split()[1]] if
param_value.split()[0] == 'o' and previous_response else param_value.split()[1]
for param_value in param_value_list
}
When i call this method for example like this:
apiRequest.set_params("lim 5 & status active")
//now self.params={"lim" : 5, "status" : "active"}
it works well. Now I want to be able to add the same parameter multiple times, and when that happens, set the param like a list:
apiRequest.set_params("lim 5 & status active & status = other")
//I want this: self.params={"lim" : 5, "status" : ["active", "other"]}
How can I modify this method beautifully? All I can think of is kinda ugly... I am new with python
Just write it as simple and straightforward as you can. That is usually the best approach. In my code, below, I made one change to your requirements: all values are a list, some may have just one element in the list.
In this method I apply the following choices and techniques:
decode and parse the previous response only once, not every time it is referenced
start with an empty dictionary
split each string only once: this is faster because it avoids redundant operations and memory allocations, and (even more importantly) it is easier to read because the code is not repetitive
adjust the value according to the special-case
use setdefault() to obtain the current list of values, if present, or set a new empty list object if it is not present
append the new value to the list of values
def set_params(self, params, previous_response=None):
if len(params) <= 0:
return
previous_data = json.loads(previous_response.decode())
self.params = {}
for param_value in params.split('&'):
key, value = param_value.split()
if key == 'o' and previous_response:
value = previous_data[value]
values = self.params.setdefault(key, [])
values.append(value)
# end set_params()
Debugging is twice as hard as writing the code in the first place.
Therefore, if you write the code as cleverly as possible, you are, by
definition, not smart enough to debug it.
— Brian W. Kernighan and P. J. Plauger in The Elements of Programming Style.
Reference: http://quotes.cat-v.org/programming/

accessing values of a dictionary with duplicate keys

I have a dictionary that looks like this:
reply = {icon:[{name:whatever,url:logo1.png},{name:whatever,url:logo2.png}]}
how do i access logo1.png ?
I tried :
print reply[icon][url]
and it gives me a error:
list indices must be integers, not str
EDIT:
Bear in mind sometimes my dictionary changes to this :
reply = {icon:{name:whatever,url:logo1.png}}
I need a general solution which will work for both kinds of dictionaries
EDIT2:
My solution was like this :
try:
icon = reply['icon']['url']
print icon
except Exception:
icon = reply['icon'][0]['url']
print ipshit,icon
This works but looks horrible. I was wondering if there was an easier way than this
Have you tried this?
reply[icon][0][url]
If you know for sure all the different kinds of responses that you will get, you'll have to write a parser where you're explicitly checking if the values are lists or dicts.
You could try this if it is only the two possibilities that you've described:
def get_icon_url(reply):
return reply['icon'][0]['url']\
if type(reply['icon']) is list else reply['icon']['url']
so in this case, icon is the key to a list, that has two dictionaries with two key / value pairs in each. Also, it looks like you might want want your keys to be strings (icon = 'icon', name='name').. but perhaps they are variables in which case disregard, i'm going to use strings below because it seems the most correct
so:
reply['icon'] # is equal to a list: []
reply['icon'][0] # is equal to a dictionary: {}
reply['icon'][0]['name'] # is equal to 'whatever'
reply['icon'][0]['url'] # is equal to 'logo1.png'
reply['icon'][1] # is equal to the second dictionary: {}
reply['icon'][1]['name'] # is equal to 'whatever'
reply['icon'][1]['url'] # is equal to 'logo2.png'
you can access elements of those inner dictionaries by either knowing how many items are in the list, and reference theme explicitly as done above, or you can iterating through them:
for picture_dict in reply['icon']:
name = picture_dict['name'] # is equal to 'whatever' on both iterations
url = picture_dict['url'] #is 'logo1.png' on first iteration, 'logo2.png' on second.
Cheers!
Not so different, but maybe it looks better (KeyError gives finer control):
icon_data = reply['icon']
try:
icon = icon_data['url']
print icon
except KeyError:
icon = icon_data[0]['url']
print ipshit,icon
or:
icon_data = reply['icon']
if isinstance(icon_data, list):
icon_data = icon_data[0]
icon = icon_data['url']

Categories

Resources