Error with Python dictionary: str object has no attribute append - python

I am writing code in python.
My input line is "all/DT remaining/VBG all/NNS of/IN "
I want to create a dictionary with one key and multiple values
For example - all:[DT,NNS]
groupPairsByKey={}
Code:
for line in fileIn:
lineLength=len(line)
words=line[0:lineLength-1].split(' ')
for word in words:
wordPair=word.split('/')
if wordPair[0] in groupPairsByKey:
groupPairsByKey[wordPair[0]].append(wordPair[1])
<getting error here>
else:
groupPairsByKey[wordPair[0]] = [wordPair[1]]

Your problem is that groupPairsByKey[wordPair[0]] is not a list, but a string!
Before appending value to groupPairsByKey['all'], you need to make the value a list.
Your solution is already correct, it works perfectly in my case. Try to make sure that groupPairsByKey is a completely empty dictionary.
By the way, this is what i tried:
>>> words = "all/DT remaining/VBG all/NNS of/IN".split
>>> for word in words:
wordPair = word.split('/')
if wordPair[0] in groupPairsByKey:
groupPairsByKey[wordPair[0]].append(wordPair[1])
else:
groupPairsByKey[wordPair[0]] = [wordPair[1]]
>>> groupPairsByKey
{'of': ['IN'], 'remaining': ['VBG'], 'all': ['DT', 'NNS']}
>>>
Also, if your code is formatted like the one you posted here, you'll get an indentationError.
Hope this helps!

Although it looks to me like you should be getting an IndentationError, if you are getting the message
str object has no attribute append
then it means
groupPairsByKey[wordPair[0]]
is a str, and strs do not have an append method.
The code you posted does not show how
groupPairsByKey[wordPair[0]]
could have a str value. Perhaps put
if wordPair[0] in groupPairsByKey:
if isinstance(groupPairsByKey[wordPair[0]], basestring):
print('{}: {}'.format(*wordPair))
raise Hell
into your code to help track down the culprit.
You could also simplify your code by using a collections.defaultdict:
import collections
groupPairsByKey = collections.defaultdict(list)
for line in fileIn:
lineLength=len(line)
words=line[0:lineLength-1].split(' ')
for word in words:
wordPair=word.split('/')
groupPairsByKey[wordPair[0]].append(wordPair[1])
When you access a defaultdict with a missing key, the factory function -- in this case list -- is called and the returned value is used as the associated value in the defaultdict. Thus, a new key-value pair is automatically inserted into the defaultdict whenever it encounters a missing key. Since the default value is always a list, you won't run into the error
str object has no attribute append anymore -- unless you have
code which reassigns an old key-value pair to have a new value which is a str.

You can do:
my_dict["all"] = my_string.split('/')
in Python,

Related

Comparing items through a tuple in Python

I am given an assignment when I am supposed to define a function that returns the second element of a tuple if the first element of a tuple matches with the argument of a function.
Specifically, let's say that I have a list of student registration numbers that goes by:
particulars = (("S12345", "John"), ("S23456", "Max"), ("S34567", "Mary"))
And I have defined a function that is supposed to take in the argument of reg_num, such as "S12345", and return the name of the student in this case, "John". If the number does not match at all, I need to print "Not found" as a message. In essence, I understand that I need to sort through the larger tuple, and compare the first element [0] of each smaller tuple, then return the [1] entry of each smaller tuple. Here's what I have in mind:
def get_student_name(reg_num, particulars):
for i in records:
if reg_num == particulars[::1][0]:
return particulars[i][1]
else:
print("Not found")
I know I'm wrong, but I can't tell why. I'm not well acquainted with how to sort through a tuple. Can anyone offer some advice, especially in syntax? Thank you very much!
When you write for i in particulars, in each iteration i is an item of the collection and not an index. As such you cannot do particulars[i] (and there is no need - as you already have the item). In addition, remove the else statement so to not print for every item that doesn't match condition:
def get_student_name(reg_num, particulars):
for i in particulars:
if reg_num == i[0]:
return i[1]
print("Not found")
If you would want to iterate using indices you could do (but less nice):
for i in range(len(particulars)):
if reg_num == particulars[i][0]:
return particulars[i][1]
Another approach, provided to help learn new tricks for manipulating python data structures:
You can turn you tuple of tuples:
particulars = (("S12345", "John"), ("S23456", "Max"), ("S34567", "Mary"))
into a dictionary:
>>> pdict = dict(particulars)
>>> pdict
{'S12345': 'John', 'S23456': 'Max', 'S34567': 'Mary'}
You can look up the value by supplying the key:
>>> r = 'S23456'
>>> dict(pdict)[r]
'Max'
The function:
def get_student_name(reg, s_data):
try:
return dict(s_data)[reg]
except:
return "Not Found"
The use of try ... except will catch errors and just return Not Found in the case where the reg is not in the tuple in the first place. It will also catch of the supplied tuple is not a series of PAIRS, and thus cannot be converted the way you expect.
You can read more about exceptions: the basics and the docs to learn how to respond differently to different types of error.
for loops in python
Gilad Green already answered your question with a way to fix your code and a quick explanation on for loops.
Here are five loops that do more or less the same thing; I invite you to try them out.
particulars = (("S12345", "John"), ("S23456", "Max"), ("S34567", "Mary"))
for t in particulars:
print("{} {}".format(t[0], t[1]))
for i in range(len(particulars)):
print("{}: {} {}".format(i, particulars[i][0], particulars[i][1]))
for i, t in enumerate(particulars):
print("{}: {} {}".format(i, t[0], t[1]))
for reg_value, student_name in particulars:
print("{} {}".format(reg_value, student_name))
for i, (reg_value, student_name) in enumerate(particulars):
print("{}: {} {}".format(i, reg_value, student_name))
Using dictionaries instead of lists
Most importantly, I would like to add that using an unsorted list to store your student records is not the most efficient way.
If you sort the list and maintain it in sorted order, then you can use binary search to search for reg_num much faster than browsing the list one item at a time. Think of this: when you need to look up a word in a dictionary, do you read all words one by one, starting by "aah", "aback", "abaft", "abandon", etc.? No; first, you open the dictionary somewhere in the middle; you compare the words on that page with your word; then you open it again to another page; compare again; every time you do that, the number of candidate pages diminishes greatly, and so you can find your word among 300,000 other words in a very small time.
Instead of using a sorted list with binary search, you could use another data structure, for instance a binary search tree or a hash table.
But, wait! Python already does that very easily!
There is a data structure in python called a dictionary. See the documentation on dictionaries. This structure is perfectly adapted to most situations where you have keys associated to values. Here the key is the reg_number, and the value is the student name.
You can define a dictionary directly:
particulars = {'S12345': 'John', 'S23456': 'Max', 'S34567': 'Mary'}
Or you can convert your list of tuples to a dictionary:
particulars = (("S12345", "John"), ("S23456", "Max"), ("S34567", "Mary"))
particulars_as_dict = dict(particulars)
Then you can check if an reg_number is in the dictionary, with they keyword in; you can return the student name using square brackets or with the method get:
>>> particulars = {'S12345': 'John', 'S23456': 'Max', 'S34567': 'Mary'}
>>> 'S23456' in particulars
True
>>> 'S98765' in particulars
False
>>>
>>> particulars['S23456']
'Max'
>>> particulars.get('S23456')
'Max'
>>> particulars.get('S23456', 'not found')
'Max'
>>>
>>> particulars['S98765']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'S98765'
>>> particulars.get('S98765')
None
>>> particulars.get('S98765', 'not found')
'not found'

update string from a dictionary with the values from matching keys

def endcode(msg,secret_d):
for ch in msg:
for key,value in secret_d:
if ch == key:
msg[ch] = value
return msg
encode('CAN YOU READ THIS',{'A':'4','E':'3','T':'7','I':'1','S':'5'})
This is my code. What I am trying to do here is for every characters in a string msg, the function should search in the dictionary and replace it with the mapping string if the character ch is a key in the dictionary secret_d.
If ch is not a key in secret_d than keep it unchanged.
For the example, the final result is should be 'C4N YOU R34D 7H15'
Your function name is endcode but you are calling encode.
But more important, I'll give you a hint to what's going on. This isn't going to totally work, but it's going to get you back on track.
def endcode(msg,secret_d):
newstr=""
for ch in msg:
for key,value in secret_d.iteritems():
if ch == key:
newstr=newstr+value
print(msg)
endcode('CAN YOU READ THIS',{'A':'4','E':'3','T':'7','I':'1','S':'5'})
But if you want a complete answer, here is mine.
A few issues:
As rb612 pointed out, there's a typo in your function definition ("endcode")
you are doing nothing with the return value of your function after calling it
msg[ch] is trying to assign items in a string, but that's not possible, strings are immutable. You'll have to build a new string. You cannot "update" it.
in order to iterate over (key, value) pairs of a dictionary d, you must iterate over d.items(). Iterating over d will iterate over the keys only.
That being said, here's my suggestion how to write this:
>>> def encode(msg, replacers):
... return ''.join([replacers.get(c, c) for c in msg])
...
>>> result = encode('CAN YOU READ THIS',{'A':'4','E':'3','T':'7','I':'1','S':'5'})
>>> result
'C4N YOU R34D 7H15'
Things to note:
dict.get can be called with a fallback value as the second argument. I'm telling it to just return the current character if it cannot be found within the dictionary.
I'm using a list comprehension as the argument for str.join instead of a generator expression for performance reasons, here's an excellent explanation.

runtime error with python dictionary while using defaultdict

I am using a dictionary to add key and values in it. I am checking if the key is already present, and if yes, I am appending the value; if not I add a key and the corresponding value.
I am getting the error message:
AttributeError: 'str' object has no attribute 'append'
Here is the code. I am reading a CSV file:
metastore_dir = collections.defaultdict(list)
with open(local_registry_file_path + data_ext_dt + "_metastore_metadata.csv",'rb') as metastore_metadata:
for line in metastore_metadata:
key = line[2]
key = key.lower().strip()
if (key in metastore_dir):
metastore_dir[key].append(line[0])
else:
metastore_dir[key] = line[0]
I found the answer on stack overflow which says to use defaultdict to resolve the issue, i am getting the error message even after the suggested anwer.
I have pasted my code for reference.
The str type has no append() method.
Replace your call to append with the + operator:
sentry_dir[key] += line[1]
It is a dictionary of strings. To declare it as a list use
if (key not in metastore_dir): ## add key first if not in dict
metastore_dir[key] = [] ## empty list
metastore_dir[key].append(line[0])
""" with defaultdict you don't have to add the key
i.e. "if key in" not necessary
"""
metastore_dir[key].append(line[0])
When you insert a new item into the dictionary, you want to insert it as a list:
...
if (key in metastore_dir):
metastore_dir[key].append(line[0])
else:
metastore_dir[key] = [line[0]] # wrapping it in brackets creates a singleton list
On an unrelated note, it looks like you are not correctly parsing the CSV. Trying splitting each line by commas (e.g. line.split(',')[2] refers to the third column of a CSV file). Otherwise line[0] refers to the first character of the line and line[2] refers to the third character of the line, which I suspect is not what you want.

Cannot append string to dictionary key

I've been programming for less than four weeks and have run into a problem that I cannot figure out. I'm trying to append a string value to an existing key with an existing string stored in it but if any value already exists in the key I get "str object has no attribute 'append'.
I've tried turning the value to list but this also does not work. I need to use the .append() attribute because update simply replaces the value in clientKey instead of appending to whatever value is already stored. After doing some more research, I understand now that I need to somehow split the value stored in clientKey.
Any help would be greatly appreciated.
data = {}
while True:
clientKey = input().upper()
refDate = strftime("%Y%m%d%H%M%S", gmtime())
refDate = refDate[2 : ]
ref = clientKey + refDate
if clientKey not in data:
data[clientKey] = ref
elif ref in data[clientKey]:
print("That invoice already exists")
else:
data[clientKey].append(ref)
break
You can't .append() to a string because a string is not mutable. If you want your dictionary value to be able to contain multiple items, it should be a container type such as a list. The easiest way to do this is just to add the single item as a list in the first place.
if clientKey not in data:
data[clientKey] = [ref] # single-item list
Now you can data[clientkey].append() all day long.
A simpler approach for this problem is to use collections.defaultdict. This automatically creates the item when it's not there, making your code much simpler.
from collections import defaultdict
data = defaultdict(list)
# ... same as before up to your if
if clientkey in data and ref in data[clientkey]:
print("That invoice already exists")
else:
data[clientKey].append(ref)
You started with a string value, and you cannot call .append() on a string. Start with a list value instead:
if clientKey not in data:
data[clientKey] = [ref]
Now data[clientKey] references a list object with one string in it. List objects do have an append() method.
If you want to keep appending to the string you can use data[clientKey]+= ref

How do I remove entries within a Counter object with a loop without invoking a RuntimeError?

from collections import *
ignore = ['the','a','if','in','it','of','or']
ArtofWarCounter = Counter(ArtofWarLIST)
for word in ArtofWarCounter:
if word in ignore:
del ArtofWarCounter[word]
ArtofWarCounter is a Counter object containing all the words from the Art of War. I'm trying to have words in ignore deleted from the ArtofWarCounter.
Traceback:
File "<pyshell#10>", line 1, in <module>
for word in ArtofWarCounter:
RuntimeError: dictionary changed size during iteration
Don't loop over all words of a dict to find a entry, dicts are much better at lookups.
You loop over the ignore list and remove the entries that exist:
ignore = ['the','a','if','in','it','of','or']
for word in ignore:
if word in ArtofWarCounter:
del ArtofWarCounter[word]
For minimal code changes, use list, so that the object you are iterating over is decoupled from the Counter
ignore = ['the','a','if','in','it','of','or']
ArtofWarCounter = Counter(ArtofWarLIST)
for word in list(ArtofWarCounter):
if word in ignore:
del ArtofWarCounter[word]
In Python2, you can use ArtofWarCounter.keys() instead of list(ArtofWarCounter), but when it is so simple to write code that is futureproofed, why not do it?
It is a better idea to just not count the items you wish to ignore
ignore = {'the','a','if','in','it','of','or'}
ArtofWarCounter = Counter(x for x in ArtofWarLIST if x not in ignore)
note that I made ignore into a set which makes the test x not in ignore much more efficient
See the following question for why your current method is not working:
Remove items from a list while iterating
Basically you should not add or remove items from a collection while you are looping over it. collections.Counter is a subclass of dict, see the following warning in the documentation for dict.iteritems():
Using iteritems() while adding or deleting entries in the dictionary may raise a RuntimeError or fail to iterate over all entries.
Use a counter, traverse the loop backwards (last to first), remove as needed. Loop until zero.

Categories