how to append in a specific line in file in python 3? - python

I am making a program for my computer science assignment. I need to make a quiz for 3 classes in a primary school. Then the results have to be saved in a file. I've done the program till here but the next task asks me to let each student have 3 turns and give an average for each one.
here is the code I used to save the results into a text file:
def savetofile():
result = result ="\n "+ namestudent.get() + " "fscore.get()+"/4"
messagebox.showinfo("results", "your results been saved successfuly")
if int(year.get())==1:
f = open('results C1.txt', 'a')
f.write(result)
f.close()
if int(year.get())==2:
f = open('results C2.txt', 'a')
f.write(result)
f.close()
if int(year.get())==3:
f = open('results C3.txt', 'a')
f.write(result)
f.close()
how can I check if the new user taking the quiz has already done the quiz or not and how can I add the new results of that person in front of their name also how can I take the average of their 3 sets of score.

first of all, you output your results been saved successfully before writing to a file, which is not true, because the file writing can fail and thus not end up done successfully.
Then you do open and close your file in the following way:
f = open('results C1.txt', 'a')
f.write(result)
f.close()
which is wrong as well because if the write fails for any reason (like an exception reading the result variable), the file won't be closed properly, and the output may not get flushed to it.
You should instead do:
with open('results C1.txt', 'a') as f:
f.write(result)
which will take care of opening, flushing and closing the file correctly.
Then the following line is wrong:
result = result ="\n "+ namestudent.get() + " "fscore.get()+"/4"
as it misses a + between the spaces and the score result. You'd better write something like (with the \n at the end to avoid having an empty first line):
result = "{} {}/4\n".format(namestudent.get(), fscore.get())
how can I check if the new user taking the quiz has already done the quiz or not?
To do it you need to do the following algorithm:
# to open the file:
with open('results C1.txt', 'r') as results:
# read the file
for result in results:
# split the line
name, score = result.split(" ")
if name == namestudent.get():
# do something when the student exists
how can I add the new results of that person in front of their name also how can I take the average of their 3 sets of score?
Then my suggestion for this is to not work directly with the files, but instead open up a "cache" dictionary of all the scores:
def load_scores():
years_tuple_default = (None, None, None) # create a tuple with as many entries as there can be years
student_scores = dict()
with open('results C1.txt', 'r') as results:
# read the file
for result in results:
# split the line
name, score = result.split(" ")
student_scores.setdefault(name, years_tuple_default)[0] = score
with open('results C2.txt', 'r') as results:
# read the file
for result in results:
# split the line
name, score = result.split(" ")
student_scores.setdefault(name, years_tuple_default)[1] = score
with open('results C3.txt', 'r') as results:
# read the file
for result in results:
# split the line
name, score = result.split(" ")
student_scores.setdefault(name, years_tuple_default)[2] = score
return student_scores
Then, create a function save the cache into the files:
def save_scores(student_scores):
with open('results C1.txt', 'w') as results_y1:
with open('results C2.txt', 'w') as results_y2:
with open('results C3.txt', 'w') as results_y3:
for student, scores in student_scores:
results_y1.write("{} {}/4\n".format(student, scores[0]))
results_y2.write("{} {}/4\n".format(student, scores[1]))
results_y3.write("{} {}/4\n".format(student, scores[2]))
print("Results saved!")
And finally, work against that cache:
def update_scores(student_scores, namestudent, fscore, year):
if namestudent.get() not in student_scores.keys():
# add the student entry
student_scores.setdefault(namestudent.get(), (None, None, None))[int(year.get())-1] = fscore.get()
else:
# update the student year entry
student_scores[int(year.get())-1] = fscore.get()
which would end up with a code looking like that to put it all together:
student_scores = load_scores()
# do your stuff to get the namestudent/fscore/year data
update_scores(student_scores, namestudent, fscore, year)
# when all updates are done, you can alter the files
save_scores(student_scores)
To sum up:
split your algorithm into functions,
protect your file handling using the with statement
read the files once to create a "cache" to work against, and save the result at once when you're done
Bonus ideas
use a class
From there, to get further, you could create a class, and have all that as methods:
class StudentScores:
def __init__(self):
self._student_scores = dict()
def load_scores(self):
# TODO
def save_scores(self):
# TODO
def update_score(self):
# TODO
And then calculating the mean of the scores is simple stupid, it's just adding a method to the class which prototype would look like:
class StudentScores:
…
def get_mean_scores(self, student):
score_y1, score_y2, score_y3 = self._student_scores[student]
score = # calculate the average of the three score
return score
use a with statement
And if you want to shine, you could implement it as working with the with statement:
class StudentScores:
def __init__(self):
self._student_scores = dict()
def load_scores(self):
# TODO
def save_scores(self):
# TODO
def update_score(self):
# TODO
def __enter__(self):
self.load_scores()
return self
def __exit__(self, type, value, traceback):
self.save_scores()
And then your code would like that:
with StudentScores() as student_scores:
# do your stuff to get the namestudent/fscore/year data
update_scores(student_scores, namestudent, fscore, year)
To conclude, I'm showing you how I'd design something to do your assignment, whereas I'm not trying to do your assignment on your behalf. My goal here is to help you get ideas and learn something about software design and python. Some things may be above your current level (like how to create a with statement), but as you're learning, you'll be able to fully understand and apply everything I'm telling you here.
HTH

Related

Creating a search function in a list from a text file

everyone. I have a Python assignment that requires me to do the following:
Download this CSV fileLinks to an external site of female Oscar winners (https://docs.google.com/document/d/1Bq2T4m7FhWVXEJlD_UGti0zrIaoRCxDfRBVPOZq89bI/edit?usp=sharing) and open it into a text editor on your computer
Add a text file to your sandbox project named OscarWinnersFemales.txt
Copy and paste several lines from the original file into your sandbox file. Make sure that you include the header.
Write a Python program that does the following:
Open the file and store the file object in a variable
Read the entire contents line by line into a list and strip away the newline character at the end of each line
Using list slicing, print lines 4 through 7 of your file
Write code that will ask the user for an actress name and then search the list to see if it is in there. If it is it will display the record and if it is not it will display Sorry not found.
Close the file
Below is the code I currently have. I've already completed the first three bullet points but I can't figure out how to implement a search function into the list. Could anyone help clarify it for me? Thanks.
f = open('OscarsWinnersFemales.txt')
f = ([x.strip("\n") for x in f.readlines()])
print(f[3:7])
Here's what I tried already but it just keeps returning failure:
def search_func():
actress = input("Enter an actress name: ")
for x in f:
if actress in f:
print("success")
else:
print("failure")
search_func()
I hate it when people use complicated commands like ([x.strip("\n") for x in f.readlines()]) so ill just use multiple lines but you can do what you like.
f = open("OscarWinnersFemales.txt")
f = f.readlines()
f.close()
data = {} # will list the actors and the data as their values
for i, d in enumerate(data):
f[i] = d.strip("\n")
try:
index, year, age, name, movie = d.split(",")
except ValueError:
index, year, age, name, movie, movie2 = d.split(",")
movie += " and " + movie2
data[name] = f"{index}-> {year}-{age} | {movie}"
print(f[3:7])
def search_actr(name):
if name in data: print(data[name])
else: print("Actress does not exist in database. Remember to use captols and their full name")
I apologize if there are any errors, I decided not to download the file but everything I wrote is based off my knowledge and testing.
I have figured it out
file = open("OscarWinnersFemales.txt","r")
OscarWinnersFemales_List = []
for line in file:
stripped_line = line.strip()
OscarWinnersFemales_List.append(stripped_line)
file.close()
print(OscarWinnersFemales_List[3:7])
print()
actress_line = 0
name = input("Enter An Actress's Name: ")
for line in OscarWinnersFemales_List:
if name in line:
actress_line = line
break
if actress_line == 0:
print("Sorry, not found.")
else:
print()
print(actress_line)

How to automatically update a list of filenames in Python

I have a list of filenames: files = ["untitled.txt", "example.txt", "alphabet.txt"]
I also have a function to create a new file:
def create_file(file):
"""Creates a new file."""
with open(file, 'w') as nf:
is_first_line = True
while True:
line = input("Line? (Type 'q' to quit.) ")
if line == "q":
# Detects if the user wants to quuit.
time.sleep(5)
sys.exit()
else:
line = line + "\n"
if is_first_line == False:
nf.write(line)
else:
nf.write(line)
is_first_line = False
I want the list to update itself after the file is created. However, if I just filenames.append() it,
I realized that it would only update itself for the duration of the program. Does anybody know how to do this? Is this possible in Python?
"Is this possible in Python?" -> This has nothing to do with limitations of the language you chose to solve your problem. What you want here is persistence. You could just store the list of files in a text file. Instead of hardcoding the list in your code your program would then read the content every time it is run.
This code could get you started:
with open("files.txt") as infile:
files = [f.strip() for f in infile.readlines()]
print(f"files: {files}")
# here do some stuff and create file 'new_file'
new_file = 'a_new_file.txt'
files.append(new_file)
###
with open("files.txt", "w") as outfile:
outfile.write("\n".join(files))

Trying to load pickled data to a list isn't appending properly

I'm writing a to-do list application, and to store the class objects task I'm pickling a list of the objects created. However, when I load the data, the list appears empty. The way I structured it is to create an empty list each session, then append the contents of the pickle file. When new tasks are created, they are appended and the whole list is then appended and then reloaded.
This is my first real software project, so my code looks pretty rough. I reviewed it and can't find any glaring errors, but obviously I am doing something wrong.
Here is the relevant code:
import _pickle as pickle
import os.path
from os import path
from datetime import datetime
#checks if data exists, and creates file if it does not
if path.exists('./tasks.txt') != True:
open("./tasks.txt", 'wb')
else:
pass
#define class for tasks
class task:
def __init__(self, name, due, category):
self.name = name
self.due = datetime.strptime(due, '%B %d %Y %I:%M%p')
self.category = category
def expand(self): # returns the contents of the task
return str(self.name) + " is due in " + str((self.due - datetime.now()))
data = []
# load data to list
def load_data():
with open('tasks.txt', 'rb') as file:
while True:
data = []
try:
data.append(pickle.load(file))
except EOFError:
break
...
# returns current task list
def list_tasks():
clear()
if not data:
print("Nothing to see here.")
else:
i = 1
for task in data:
print("%s. %s" % (i, task.expand()))
i = i+1
#define function to add tasks
def addTask(name, due, category):
newTask = task(name, due, category)
data.append(newTask)
with open('./tasks.txt', 'wb') as file:
pickle.dump(data, file)
load_data()
list_tasks()
...
load_data()
list_tasks()
startup()
ask()
data = []
# load data to list
def load_data():
with open('tasks.txt', 'rb') as file:
while True:
data = []
try:
data.append(pickle.load(file))
except EOFError:
break
That second data = [] doesn't look right. Having data = [] both inside and outside of the function creates two data objects, and the one you're appending to won't be accessible anywhere else. And even if it was accessible, it would still be empty since it's being reset to [] in every iteration of the while loop. Try erasing the inner data = []. Then the data.append call will affect the globally visible data, and its contents won't be reset in each loop.
Additionally, going by the rest of your code it looks like that data is supposed to be a list of tasks. But if you pickle a list of tasks and then run data.append(pickle.load(file)), then data will be a list of lists of tasks instead. One way to keep things flat is to use extend instead of append.
data = []
# load data to list
def load_data():
with open('tasks.txt', 'rb') as file:
while True:
try:
data.extend(pickle.load(file))
except EOFError:
break
I think it may also be possible to load the data with a single load call, rather than many calls in a loop. It depends on whether your tasks.txt file is the result of a single pickle.dump call, or if you appended text to it multiple times with multiple pickle.dump calls while the file was opened in "append" mode.
def load_data():
with open('tasks.txt', 'rb') as file:
return pickle.load(file)
data = load_data()

Why won't my readline advance to the next line?

Whenever my methods are supposed to be advancing to the next line in a txt file that I am importing, they instead decide to continuously use the same line instead of advancing to the next line in the document.
DUMMY = 9999
def readMaster():
#opens the customers file, sets a variable to whatever line we are on
infile=open("customers.txt", 'r')
line=infile.readline()[:-1]
#checks if entered ID is valid. If it is, return their name and balance. if not, return garbage.
if line!=(""):
masterID,masterName,balance=line.split(",")
return int(masterID),masterName,int(balance)
else:
masterID=DUMMY
return masterID,"",0
infile.close()
def readTransaction():
#opens the transactions files
infile=open("transactions.txt","r")
#scans through the transactions file. If Item ID is found, return that line
#if it isn't, return garbage variables.
line=infile.readline()[:-1]
if line!=(""):
itemID,itemName,cost=line.split(",")
return int(itemID),itemName,int(cost)
else:
itemID=DUMMY
return itemID,"",0
infile.close()
def updateRecords():
#creates a new file for us to write to.
outfile = open("new_master.txt", 'w')
#pulls in any values we need for calculation
masterID,masterName,balance = readMaster()
itemID,itemName,cost=readTransaction()
#checks to see if the customer's ID matches the ID of the service purchased. To avoid printing multiple lines
#per person, we use a while loop to continue adding to the balance until the customer didn't buy the next item.
#Then, asks for the next line in the transaction text.
if int(itemID)==int(masterID):
while int(itemID)==int(masterID):
balance = balance+cost
return int(itemID),itemName,int(cost)
# Since the customers.txt and transactions.txt files are both sorted numerically, we check to see
# if one is greater than the other. If it is, that means a customer didn't make any purchases, so we
# print that person's line from customers.txt without updating it
elif itemID>masterID:
print(masterID+","+masterName+","+balance,file =outfile)
# If we don't find any transactions for something, an error is printed.
else:
print("No record for item",itemID)
print(masterID + "," + masterName + "," + balance, file=outfile)
itemID,itemName,cost=readTransaction()
#Then, we print the customer's ID, name, and new balance to the new text file
print (masterID+","+masterName+","+balance,file = outfile)
Customers.txt
207,Ann Wyeth,120
215,David Fisher,89
412,Deb Washington,75
609,Lily Ahn,110
610,Dottie Sturgis, 39
1984,Leslie Jackson,109
1989,Taylor Grandview,55
1999,Roger Nelson,65
2112,Lee Geddy,99
5150,Valerie Edwards,45
7800,John Bongiovi,160
transactions.txt
207,Formal Styling,55
207,Partial Highlights,65
215,Haircut,29
610,Formal Styling,55
610,Accent Highlights,50
1999,Clipper Cut,19
2112,Haircut with Shampoo,39
5150,Haircut with Styling,45
5150,Partial Highlights,65
5150,Treatments,29
6792,Coloring,150
7800,Haircut,29
You're not looping over the file's contents. In each of your methods your opening the file and then doing a readline, so it seems you're asking it to repeatedly open the file and read only the first line. For example, in readMaster you think it's doing:
opens the customers file, sets a variable to whatever line we are on
But that's not the case. You're opening the file, reading one line of it, checking for a null string, and then returning some tuple before closing the file. No matter how many times you call this method, it will only ever read the first line.
Since you're basically doing the same sort of read operation on both files (returning the int(line[0]),line[1],int(line[2]) for each line, you can use a single method (if you needed to process differently, you could use boolean switch based on the filename, etc.):
def readFile(filename):
# returns data from specified file
with open(filename, 'r') as infile:
lines = [line.trim() for line in infile.readlines()]
lines = [(int(ln[0]),ln[1],int(ln[2])) for ln.split(',') in lines if ln else (masterID,'',0)]
return lines
I'm not sure what sort of output you expect, but I have some idea this might be what you're after:
customers = r'c:\debug\customers.txt'
transactions = r'c:\debug\transactions.txt'
outputFile = r'c:\debug\new_master.txt'
def readFile(filename):
DUMMY = 9999
default = [DUMMY,'',0]
# opens the customers file, and returns a list of tuple OR
# opens the transactions file and returns a list of tuple
with open(filename, 'r') as infile:
lines = [line.strip().split(',') for line in infile.readlines()]
lines = [ln[:3] if ln else default for ln in lines]
return lines
def updateRecords():
"""
checks to see if the customer's ID matches the ID of the service purchased.
To avoid printing multiple lines per person, add to the balance for each matching id.
"""
#pulls in any values we need for calculation
master = readFile(customers)
trans = readFile(transactions)
#creates a new file for us to write to.
outfile = open(outputFile, 'w')
for (id,name,bal) in master:
balance = int(bal)
balance += sum(int(itmCost) for (itmID,itmName,itmCost) in trans if itmID == id)
# now we have the balance for THIS id from customer file
if balance == int(bal):
# If we don't find any transactions for something, an error is printed.
# balance hasn't changed, no transaction for this customer, log to console
print("No record for item {}".format(id))
# update the new master file:
outfile.write('{},{},{}\n'.format(id,name,balance))
outfile.close()
And produces the following output file:
207,Ann Wyeth,240
215,David Fisher,118
412,Deb Washington,75
609,Lily Ahn,110
610,Dottie Sturgis,144
1984,Leslie Jackson,109
1989,Taylor Grandview,55
1999,Roger Nelson,84
2112,Lee Geddy,138
5150,Valerie Edwards,184
7800,John Bongiovi,189

Write to file python - Linebreak issue (\n)

I have a problem where the function just overwrites the line thats already there in a .txt file. The function is supposed to write a highscore to a file when the game quits (I have made a snake game by following a youtube tutorial). I can't quite figure out why it won't start on a new line, can anyone please explain the logic behind it, and how I fix it? I read somewhere that instead of "w" in f.open(), I should type "rb" or something. Since I'm kinda new to this "writing-to-file" thingy, I find it difficult.
Also, I want to sort the highscores from highest to lowest in the file (in other words, sort finalScore from highest to lowest). I have no idea how I should go on and code that, so I'd appreicate some help. You see, I want to print out the current highscores to the console (in order to make a scoreboad)
Heres the code:
import random
import time
name = "Andreas"
finalScore = random.randint(1,10)
def scoreToFile(finalScore):
#Has to be generated here, since we need the exact current time
currentTime = time.strftime("%c")
print("Sucsessfully logged score (finalScore) to highscores.txt")
f = open("highscores.txt", "w")
#fileOutput = [(currentTime, ":", name, "-", finalScore)]
fileOutput = [(finalScore, "-", name, currentTime)]
for t in fileOutput:
line = ' '.join(str(x) for x in t)
f.write(line + "\n")
f.close()
scoreToFile(finalScore)
Anyways, merry christmas my fellow python geeks! :D
1) one option is to open the file in append mode.
replace:
f = open("highscores.txt", "w")
with:
f = open("highscores.txt", "a")
2) another option is to replace this block,
f = open("highscores.txt", "w")
#fileOutput = [(currentTime, ":", name, "-", finalScore)]
fileOutput = [(finalScore, "-", name, currentTime)]
for t in fileOutput:
line = ' '.join(str(x) for x in t)
myfile.write(line + "\n")
f.close()
and use a with style
with open("highscores.txt", "a") as myfile:
#fileOutput = [(currentTime, ":", name, "-", finalScore)]
fileOutput = [(finalScore, "-", name, currentTime)]
for t in fileOutput:
line = ' '.join(str(x) for x in t)
myfile.write(line + "\n")
I prefer the second style as it is more safe and clean.
Mode w overwrites an existing file; mode 'a' appends to it. Also, the best way to handle a file is usually with the with statement, which ensures the closing on your behalf; so:
fileOutput = [(finalScore, "-", name, currentTime)]
with open("highscores.txt", "a") as f:
for t in fileOutput:
line = ' '.join(str(x) for x in t)
f.write(line + "\n")
For sorting, you need be able to extract the final score as a number from a line:
def minus_score(line):
return -int(line.split()[0])
then the total work will be done as:
def sorted_by_score():
with open("highscores.txt", "r") as f:
result = list(f)
return sorted(result, key=minus_score)
This will give you a list lines sorted in ascending order of score (the latter's the reason score is negating the number, though one might also choose to have it just return the number and reverse the sorting), for you to loop on and further process.
Added: so on the OP's request here's how the whole program might be (assuming the existence of a function that either plays a game and returns player name and final score, or else returns None when no more games are to be played and the program must exit).
import time
def play_game():
""" play a game and return name, finalscore;
return None to mean no more games, program finished.
THIS function you'd better code yourself!-)
"""
def scoreToFile(name, finalScore):
""" Add a name and score to the high-scores file. """
currentTime = time.strftime("%c")
fileOutput = finalScore, "-", name, currentTime
line = ' '.join(str(x) for x in fileOutput)
with open("highscores.txt", "a") as f:
f.write(line + "\n")
def minus_score(line):
""" just for sorting purposes, not called directly. """
return -int(line.split()[0])
def sorted_by_score():
""" return list of score lines sorted in descending order of score. """
with open("highscores.txt", "r") as f:
return sorted(f, key=minus_score)
def main():
while True:
game_result = play_game()
if game_result is None: break
scoreToFile(*game_result)
for line in sorted_by_score:
print(line.strip())
As others have mentioned, the problem is you're not opening the file in append mode, so it overwrites it every time rather than adding to it.
However, if you also want to keep the data in the file sorted, you do want to overwrite it each time, since the order of its contents will likely have been changed with the addition. To do that requires first reading it contents in, updating the data, and then writing it back out.
Here's a modified version of your function that does that. I also changed how the data in file is stored to what is known as Comma (or Character) Separated Values (CSV) format, because Python includes acsvmodule which makes it very easy to read, write, and do other things with such files.
import csv
import random
import time
highscores_filename = "highscores.txt"
HighScoresFirst = True # Determines sort order of data in file
def scoreToFile(name, finalScore):
currentTime = time.strftime("%c")
# Try reading scores from existing file.
try:
with open(highscores_filename, "r", newline='') as csvfile:
highscores = [row for row in csv.reader(csvfile, delimiter='-')]
except FileNotFoundError:
highscores = []
# Add this score to the end of the list.
highscores.append([str(finalScore), name, currentTime])
# Sort updated list by numeric score.
highscores.sort(key=lambda item: int(item[0]), reverse=HighScoresFirst)
# Create/rewrite highscores file from highscores list.
with open(highscores_filename, "w", newline='') as csvfile:
writer = csv.writer(csvfile, delimiter='-')
writer.writerows(highscores)
print("successfully logged score (finalScore) to highscores.txt")
# Simulate using the function several times.
name = "Name"
for i in range(1, 4):
finalScore = random.randint(1,10)
scoreToFile(name + str(i), finalScore)
time.sleep(random.randint(1,3)) # Pause so time values will vary.

Categories

Resources