Why do I get a KeyError when using writerow() but not when using print()?
Question:
I’m designing a program that works as designed to take a csv file in the form of last name, first name, Harry Potter house and write it to another csv file in the form of first name, last name, house. It works fine when I print it locally to my terminal (when the part commented out is used), but throws the following error when I run it as is.
Traceback (most recent call last):
File "/workspaces/107595329/scourgify/scourgify.py", line 26, in <module>
writer.writerow(row[first], row[last], row[house])
KeyError: ' Hannah'
Hannah being the first of the first names in the file. How can I re-write the writer.writerow() line(line 26) to write first, last, and house to my after.csv file?
Code here:
import csv
import sys
students = []
try:
if len(sys.argv) < 3:
print("Too few command line arguments")
sys.exit(1)
if len(sys.argv) > 3:
print("Too many command line arguments")
sys.exit(1)
if not sys.argv[1].endswith(".csv") or not sys.argv[2].endswith(".csv"):
print("Not a CSV file")
sys.exit(1)
elif sys.argv[1].endswith(".csv") and sys.argv[2].endswith(".csv"):
with open(sys.argv[1]) as file:
reader = csv.DictReader(file)
for row in reader:
students.append({"name": row["name"], "house": row["house"]})
with open(sys.argv[2], "w") as after:
writer = csv.DictWriter(after, fieldnames = ["first, last, house"])
for row in students:
house = row["house"]
last, first = row["name"].split(",")
writer.writerow(row[first], row[last], row[house])
#print(first, last, house)
except FileNotFoundError:
print("Could not read " + sys.argv[1])
sys.exit(1)
Answers:
Is it possible that you have a "Hannah" entry, while you actually are looking for a " Hannah" entry (with a whitespace as first character)?
That would make sense, since the print statement uses your "first", "last" and "house" variables, while the writerow command reads entries from a dictionary first. The split command in
last, first = row["name"].split(",")
will then probably output ["lastname", " firstname"] as the data probably is stored as "lastname, firstname", right?
I’d recommend using string.strip() or something similar to manage your keys properly
This is the line of code in question:
writer.writerow(row[first], row[last], row[house])
Per the error message you’re getting, one of first
, last
or house
(first
, I’m guessing) has the value "Hannah"
, and the error is due to an attempt to find a key with that value in row
. The error is saying that row
has no key named `"Hannah".
So what is row
in this context? Well, looking at your code, row
is each time through the surrounding for
loop one of the values in the list students
. So then what is in students
? Well, values are added to students
via the following line of code:
students.append({"name": row["name"], "house": row["house"]})
So students
is a list of dicts, each of which contains two keys, "name"
and "house"
. So it seems that any particular element in students
is never going to be a dict containing the key "Hannah"
. There’s your problem.
The two lines of code just above this line have it right:
house = row["house"]
last, first = row["name"].split(",")
Here, the two keys we know are in each element in row
are being properly extracted. So at this point, you should be done with row
. row
contains nothing more than exactly what you’ve already read from it.
…and what do you know! Your commented out print()
statement had it right all along. I assume that what you really want here is:
writer.writerow(first, last, house)
Some notes about the OP code:
with open(sys.argv[1]) as file:
reader = csv.DictReader(file)
for row in reader:
students.append({"name": row["name"], "house": row["house"]})
# "row" is already the dictionary being formed here, so
# students.append(row) is all that is needed
# Could also do the following and skip the for loop
# students = list(reader)
with open(sys.argv[2], "w") as after:
# fieldnames is wrong.
# fieldnames = ['first','last','house'] is what is needed
writer = csv.DictWriter(after, fieldnames = ["first, last, house"])
for row in students:
house = row["house"]
last, first = row["name"].split(",")
# DictWriter.writerow takes a single dictionary as a parameter.
# correct would be:
# writer.writerow({'first':first,'last':last,'house':house})
writer.writerow(row[first], row[last], row[house])
Frankly, DictReader
is overkill for this task. Open both files at once and re-write each line as it is read with using the standard reader/writer objects.
An input file wasn’t provided, so given the following from the code description:
input.csv
name,house
"Potter, Harry",Gryffindor
"Weasley, Ron",Gryffindor
"Malfoy, Draco",Slytherin
"Lovegood, Luna",Ravenclaw
This code will process as needed:
test.py
import csv
# Note: newline='' is a requirement for the csv module when opening files.
# Without ends up writing rrn line endings on Windows.
with (open('input.csv', newline='') as file,
open('output.csv', 'w', newline='') as after):
reader = csv.reader(file)
next(reader) # skip header
writer = csv.writer(after)
writer.writerow(['first', 'last', 'house']) # write new header
for name, house in reader:
last, first = name.split(', ')
writer.writerow([first, last, house])
output.csv
first,last,house
Harry,Potter,Gryffindor
Ron,Weasley,Gryffindor
Draco,Malfoy,Slytherin
Luna,Lovegood,Ravenclaw
I’m designing a program that works as designed to take a csv file in the form of last name, first name, Harry Potter house and write it to another csv file in the form of first name, last name, house. It works fine when I print it locally to my terminal (when the part commented out is used), but throws the following error when I run it as is.
Traceback (most recent call last):
File "/workspaces/107595329/scourgify/scourgify.py", line 26, in <module>
writer.writerow(row[first], row[last], row[house])
KeyError: ' Hannah'
Hannah being the first of the first names in the file. How can I re-write the writer.writerow() line(line 26) to write first, last, and house to my after.csv file?
Code here:
import csv
import sys
students = []
try:
if len(sys.argv) < 3:
print("Too few command line arguments")
sys.exit(1)
if len(sys.argv) > 3:
print("Too many command line arguments")
sys.exit(1)
if not sys.argv[1].endswith(".csv") or not sys.argv[2].endswith(".csv"):
print("Not a CSV file")
sys.exit(1)
elif sys.argv[1].endswith(".csv") and sys.argv[2].endswith(".csv"):
with open(sys.argv[1]) as file:
reader = csv.DictReader(file)
for row in reader:
students.append({"name": row["name"], "house": row["house"]})
with open(sys.argv[2], "w") as after:
writer = csv.DictWriter(after, fieldnames = ["first, last, house"])
for row in students:
house = row["house"]
last, first = row["name"].split(",")
writer.writerow(row[first], row[last], row[house])
#print(first, last, house)
except FileNotFoundError:
print("Could not read " + sys.argv[1])
sys.exit(1)
Is it possible that you have a "Hannah" entry, while you actually are looking for a " Hannah" entry (with a whitespace as first character)?
That would make sense, since the print statement uses your "first", "last" and "house" variables, while the writerow command reads entries from a dictionary first. The split command in
last, first = row["name"].split(",")
will then probably output ["lastname", " firstname"] as the data probably is stored as "lastname, firstname", right?
I’d recommend using string.strip() or something similar to manage your keys properly
This is the line of code in question:
writer.writerow(row[first], row[last], row[house])
Per the error message you’re getting, one of first
, last
or house
(first
, I’m guessing) has the value "Hannah"
, and the error is due to an attempt to find a key with that value in row
. The error is saying that row
has no key named `"Hannah".
So what is row
in this context? Well, looking at your code, row
is each time through the surrounding for
loop one of the values in the list students
. So then what is in students
? Well, values are added to students
via the following line of code:
students.append({"name": row["name"], "house": row["house"]})
So students
is a list of dicts, each of which contains two keys, "name"
and "house"
. So it seems that any particular element in students
is never going to be a dict containing the key "Hannah"
. There’s your problem.
The two lines of code just above this line have it right:
house = row["house"]
last, first = row["name"].split(",")
Here, the two keys we know are in each element in row
are being properly extracted. So at this point, you should be done with row
. row
contains nothing more than exactly what you’ve already read from it.
…and what do you know! Your commented out print()
statement had it right all along. I assume that what you really want here is:
writer.writerow(first, last, house)
Some notes about the OP code:
with open(sys.argv[1]) as file:
reader = csv.DictReader(file)
for row in reader:
students.append({"name": row["name"], "house": row["house"]})
# "row" is already the dictionary being formed here, so
# students.append(row) is all that is needed
# Could also do the following and skip the for loop
# students = list(reader)
with open(sys.argv[2], "w") as after:
# fieldnames is wrong.
# fieldnames = ['first','last','house'] is what is needed
writer = csv.DictWriter(after, fieldnames = ["first, last, house"])
for row in students:
house = row["house"]
last, first = row["name"].split(",")
# DictWriter.writerow takes a single dictionary as a parameter.
# correct would be:
# writer.writerow({'first':first,'last':last,'house':house})
writer.writerow(row[first], row[last], row[house])
Frankly, DictReader
is overkill for this task. Open both files at once and re-write each line as it is read with using the standard reader/writer objects.
An input file wasn’t provided, so given the following from the code description:
input.csv
name,house
"Potter, Harry",Gryffindor
"Weasley, Ron",Gryffindor
"Malfoy, Draco",Slytherin
"Lovegood, Luna",Ravenclaw
This code will process as needed:
test.py
import csv
# Note: newline='' is a requirement for the csv module when opening files.
# Without ends up writing rrn line endings on Windows.
with (open('input.csv', newline='') as file,
open('output.csv', 'w', newline='') as after):
reader = csv.reader(file)
next(reader) # skip header
writer = csv.writer(after)
writer.writerow(['first', 'last', 'house']) # write new header
for name, house in reader:
last, first = name.split(', ')
writer.writerow([first, last, house])
output.csv
first,last,house
Harry,Potter,Gryffindor
Ron,Weasley,Gryffindor
Draco,Malfoy,Slytherin
Luna,Lovegood,Ravenclaw