Passing a variable between two functions - python

The following is a piece of code that I have been working on for awhile. I've been able to compile and run the code without error. However, I am having a difficult time with passing a variable from one function to another in my code.
The problem seems to occur after I run choose() and create self.newLists based on the desired indices. You'll notice that I added print(self.newLists) at the end of this function so that I can check to see if it is producing what I want.
The next function, simplify(), is where my issue arises. When I try to pass self.newLists from the previous function it doesn't seem to produce anything. I also tried printing and/or returning the variable named answer but it returns "none". I've been stumbling over this obstacle for awhile without any progress. Below is the code I am working on along with an example of what I want simplify() to produce.
from tkinter import *
from tkinter.filedialog import askopenfilename
class myFileOpener:
def __init__(self, master):
frame = Frame(master)
frame.pack()
print()
self.newLists = ()
self.printButton = Button(frame, text="Select File", command=self.openfile)
self.printButton.pack(side=LEFT)
self.runButton = Button(frame, text="Run", command=self.combine)
self.runButton.pack(side=LEFT)
self.quitButton = Button(frame, text="Quit", command=frame.quit)
self.quitButton.pack(side=LEFT)
def openfile(self):
filename = askopenfilename(parent=root)
self.lines = open(filename)
# print(self.lines.read())
def choose(self):
g = self.lines.readlines()
for line in g:
matrix = line.split()
JD = matrix[2]
mintime = matrix[5]
maxtime = matrix[7]
self.newLists = [JD, mintime, maxtime]
print(self.newLists)
def simplify(self):
dates = {}
for sub in self.newLists:
date = sub[0]
if date not in dates:
dates[date] = []
dates[date].extend(sub[1])
answer = []
for date in sorted(dates):
answer.append([date] + dates[date])
return answer
def combine(self):
self.choose()
self.simplify()
root = Tk()
b = myFileOpener(root)
root.mainloop()
Example of desired output from simplify():
[['2014-158', '20:07:11.881', '20:43:04.546', '20:43:47.447', '21:11:08.997', '21:11:16.697', '21:22:07.717'],
['2014-163', '17:12:09.071', '17:38:08.219', '17:38:28.310', '17:59:25.649', '18:05:59.536', '18:09:53.243', '18:13:47.671', '18:16:53.976', '18:20:31.538', '18:23:02.243']]
It essentially groups times by certain dates.

You are not producing a list of lists. You are resetting self.newLists each loop iteration, to a single list with 3 elements:
for line in g:
matrix = line.split()
JD = matrix[2]
mintime = matrix[5]
maxtime = matrix[7]
self.newLists = [JD, mintime, maxtime]
You need to instead use list.append() to add those 3 elements to a list you set once, outside of the loop:
self.newLists = []
for line in g:
matrix = line.split()
JD = matrix[2]
mintime = matrix[5]
maxtime = matrix[7]
self.newLists.append([JD, mintime, maxtime])
Your simplify method is adding the individual characters of mintime to your output lists:
for sub in self.newLists:
date = sub[0]
if date not in dates:
dates[date] = []
dates[date].extend(sub[1])
You want to use list.append() there, not list.extend(). That loop can be simplified using dict.setdefault() rather than test for the key manually:
for date, mintime, maxtime in self.newLists:
dates.setdefault(date, []).append(mintime)

Related

Tkinter: multiple choice quiz app, can't change question (and other issues)

I'm trying to create a multiple choice question app with tkinter but I'm having three problems that I can't solve by myself:
The question don't change, it stuck on the first question.
I have a file with all my questions (more then 50) what I'd like to do is select randomly only 10 of them from the list (five from easy_question list and 5 from the hard_question list).
Is there a way to save in an exel file the ten code of the question that where selected and to know what that person answered (wrong or right doesn't metter)? something like this:
1 2 3 [...]
question 1e 3h 2e
answer 2 3 4
This is a simple version of my file with all my questions, options and correct answers:
easy_questions2=[
"1e. Name?",
"2e. Last name?",
"3e. Birthdate?",
"4e. Food?"
]
easy_options=[
['Marck', 'Mary','Joseph','John'],
['Smith', 'Hartnett','Pitt','Pacino'],
['June', 'October','November','April'],
['All', 'Fries','Pasta','Chicken']
]
easy_answers=[
1,
2,
3,
3
]
hard_questions2=[
"1h. Number?",
"2h. Word?",
"3h. Hour?",
"4h. Color?"
]
hard_options=[
['10', '11','21','55'],
['Book', 'Table','en','Pacino'],
['11', '21','24','18'],
['Yellow', 'Blue','Red','Green']
]
hard_answers=[
3,
4,
1,
2
]
This is my code:
import tkinter as tk
import tkinter.ttk as ttk
from questionslist import easy_questions2, easy_answers, easy_options
from openpyxl import load_workbook
from tkinter import messagebox as mb
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.q_no=0
self.display_title()
self.display_question()
self.opt_selected=tk.IntVar()
self.opts=self.radio_buttons()
self.display_options()
self.buttons()
self.data_size=len(question)
self.correct=0
def display_result(self):
wrong_count = self.data_size - self.correct
correct = f"Correct: {self.correct}"
wrong = f"Wrong: {wrong_count}"
score = int(self.correct / self.data_size * 100)
result = f"Score: {score}%"
mb.showinfo("Result", f"{result}\n{correct}\n{wrong}")
def check_ans(self, q_no):
if self.opt_selected.get() == answer[q_no]:
return True
def next_button(self):
if self.check_ans(self.q_no):
self.correct += 1
self.q_no += 1
if self.q_no==self.data_size:
self.display_result()
self.submit()
self.destroy()
else:
self.display_question()
self.display_options()
def buttons(self):
next_button = tk.Button(self, text="Next",command=self.next_button, width=10)
next_button.pack(pady=50, side="bottom")
def display_options(self):
val=0
self.opt_selected.set(0)
for option in options[self.q_no]:
self.opts[val]['text']=option
val+=1
def display_question(self):
q_no = tk.Label(self, text=question[self.q_no], width=60)
q_no.pack(padx=19, pady=31, anchor="w")
def radio_buttons(self):
q_list = []
while len(q_list) < 4:
radio_button = ttk.Radiobutton(self,text=" ",variable=self.opt_selected,
value = len(q_list)+1)
q_list.append(radio_button)
radio_button.pack(padx=19, anchor="w")
return q_list
def submit(self):
wb = load_workbook('D:\\Python\\quiz\\template.xlsx')
sheet = wb.active
sheet.cell(row=1, column=2).value = "first_name"
sheet.cell(row=1, column=3).value = "correct"
sheet.cell(row=1, column=4).value = "wrong"
sheet.cell(row=2, column=3).value = self.correct
sheet.cell(row=2, column=4).value = self.data_size - self.correct
excel_filename = "D:\\Python\\quiz\\" + "data" + ".xlsx"
wb.save(excel_filename)
question = easy_questions2
options = easy_options
answer = easy_answers
if __name__ == "__main__":
root = tk.Tk()
MainApplication(root).pack(side="top", fill="both", expand=True)
root.title("Quiz")
root.geometry('800x450')
root.mainloop()
If you want to read the questions from a file (or more), you could use (suppose the file is a .txt and you've wrote a question per line):
question_list = []
with open('filename.txt', 'r+') as question_file:
lines = question_file.readlines()
# Let's move the pointer of the file back to the start to resave the questions
# as we don't want to lose them.
question_file.seek(0)
question_file.truncate()
for line in lines:
question_list.append(line) # Put each question at the end of the list.
question_file.write(line)
Use this method to retrieve all the questions and answers from all your files and to put them into lists.
Now you should have a list for the easy questions and one for the hard questions, so, to pick a random question from one list you can use a specific function from the random module, randint(a, b) which is a function that return a value in between 'a' and 'b' ('a' and 'b' included). Try it this way:
from random import randint
# You have a list of 25 hard question, let's call it hard_list
# and a list of 25 easy question, let's call it easy_list
# Now let's use our randint function to retrieve 10 random value between 0 and 24.
# As you haven't done any statement about how to choose the order of the questions,
# I'll take the first five from the hards one and the last 5 from the easy,
# but you can do it in any way you want.
# It may be a little tricky to manage the possibility of same random numbers to come out,
# surely there are better ways to do it, but this works fine.
random_questions = [] # Here I write the questions randomly chosen.
random_numbers = [] # I use this to take into account the random values.
i = 0 # Our counter.
while i < 10:
# If 5 questions have already been chosens, we change the list
# and clear the random values counter once.
if i >= 5:
if i == 5:
random_numbers.clear()
val = randint(0, 24)
if val not in random_numbers:
i += 1
random_numbers.append(val)
random_questions.append(easy_list[val])
else:
val = randint(0, 24)
if val not in random_numbers:
i += 1
random_numbers.append(val)
random_questions.append(hard_list[val])
Now, with your code, I see that the 'check_button' function doesn't check if the user have select an option or not, but maybe it's what you wanted, secondly, your button don't change the question because.. you're assigning a new Label to q_no (which, I suppose, is the number of questions answered so far)? You should assign the Label to another variable and update its text value each time the user click the button.
For example:
self.question_label = tk.Label(self, text=question[self.q_no], ....)
self.question_label.pack(...)
And when the user press the button to the next question, you check if the answer is correct, then:
self.q_no += 1
if self.q_no==(self.data_size + 1):
#
# Do what you want to do if that was the last question.
#
else:
self.question_label['text'] = question[self.q_no]
#
# Here change the value of the buttons with the answers.
#
For the third question, what format do you exactly want? There are differences between .csv, .xlsx, .xlsb, etc. But for the conversion you can use the pandas module.
These methods need a specific type of object for the conversion, so be shure to format your data in that specific way before using them:
to csv to xlsx. This is the class you can use as data structure.
From personal experience I know that you can format your datas as json to convert them to csv, but I'm not shure if it works for xlsx and other formats too.
Hope I've helped a little bit.

How to retrieve lines from text widget individually

I am trying to retrieve each new line in a text widget separately, I have 3 instances of Container class, the first instance prints data as expected but for second instance duplicates of first line are returned
I am using object.get('current linestart', 'current lineend') to return new lines separately
full code: https://pastebin.com/mLR3zbFg
class Container(tk.Frame):
def __init__(self, parent = None, priority = 3, bg = 'bisque'):
self.inpList = []
self.b = tk.Button(self.f, text = 'add', command = lambda: self.add_task(priority))
def add_task(self, priority): # refer 1.2_text for implementation
finished = False
self.t = tk.Text(self.f)
self.t.configure(height = 10, wrap = 'word')
self.t.bind("<Return>", self.save_task)
self.t.pack(fill = tk.X)
def print_all(self):
print(self.inpList)
def save_task(self, event):
td = self.t.get('current linestart', 'current lineend')
self.inpList.append(td)
if __name__ == '__main__':
window = tk.Tk()
window.minsize(300, 600)
p1 = Container(window, priority = 1)
p2 = Container(window, bg = 'blue', priority = 2)
p3 = Container(window, bg = 'red', priority = 3)
window.mainloop()
figbeam's answer could work, but it wouldn't be great if you had a lot of text inputted and it seems like you want to read each line individually. Here's a better solution in my opinion:
According to the docs, current doesn't seem to do what you expect; namely, current will give you to the character closest to your mouse (and only if you actually move your mouse). This could be a reason why you were noticing weird behavior for the widgets that were below but not the one on top.
A better solution is to move to the end of the text, then up one line, and then use the linestart and lineend selectors you had prior. Namely change
td = self.t.get('current linestart', 'current lineend')
to
td = self.t.get('end - 1 lines linestart', 'end - 1 lines lineend')
Things should work as expected after this change!
Is it necessary to read each line separately? Otherwise you could read the lines into a list and then work with the list items separately.
As far as I can tell the delimiter for lines is newline, which can make it look odd if you use wrap.
Here's an example of reading multiple lines into a list:
line_list = textbox.get('1.0', 'end').split('\n')
for line in line_list:
# Process line
Just a thought.
The line td = self.t.get('current linestart', 'current lineend') doesn't work as expected. One solution is to read the whole content of the text box at each update (as suggested also in https://stackoverflow.com/a/55739714/2314737).
In the code substitute the function save_task() with:
def save_task(self, event):
td = self.t.get("1.0",'end').rstrip()
self.inpList = td.split('\n')
This also takes care of any deleted lines that otherwise will stay in td.
See also this similar question: How to read the input(line by line) from a multiline Tkinter Textbox in Python?

Altering python variable identifiers in a loop which defines a tkinter button

I've got a function in a class in which I am creating several tkinter buttons within, where the number of buttons and the button properties depends on a file (so I can't create a specific number of variables to hold the buttons).
The code I have looks like this (sparing the complexities of the whole code):
import os
import tkinter as tk
Class(GUI):
def ButtonCreator(self):
self.HomeworkList = open("Files\HWNameList.txt", "r")
x = self.HomeworkList.readline()
while not x == "END":
x = x[0:-1]
HomeworkFileName = str("Files\HW-" + x + ".txt")
locals()["self.Button" + x] = tk.Button(master, text = x, command = lambda: self.DisplayHomeworkFile(FileName))
locals()["self.Button" + x].pack()
x = self.HomeworkList.readline()
self.HomeworkList.close()
def DisplayHomeworkFile(self, filename):
os.startfile(filename)
The file I am opening looks like this...
HomeworkName1
HomeworkName2
HomeworkName3
END
When the code runs, it displays the buttons with the correct text written on them but when i click on them they only ever display the file who's file name is written last in the HomeworkList file. Not sure what I've done wrong.
If there is another way to achieve what I'm trying I'm open to all suggestions.
Thanks.
This is a classic beginners problem that comes from misunderstanding how lambda works. For this case you need to use functools.partial.
You also need to forget about modifying locals(). Make a list or dictionary to hold the button instances.
from functools import partial
def ButtonCreator(self):
self.HomeworkList = open("Files\HWNameList.txt", "r")
x = self.HomeworkList.readline()
self.buttons = []
while not x == "END":
x = x[0:-1]
HomeworkFileName = str("Files\HW-" + x + ".txt")
btn = tk.Button(master, text = x, command = partial(self.DisplayHomeworkFile, HomeworkFileName))
btn.pack()
self.buttons.append(btn)
x = self.HomeworkList.readline()
self.HomeworkList.close()

How to dynamically add remove and delete labels created in a for loop from text file

I am trying to display a list of users saved in a text file on a line by line basis and have it updated as people launch and close my program respectively. I can't figure out how to update the labels as in create new ones and delete ones no longer present in the text file as I can't .set() or .config() them as far as I know since the actual text on the labels doesn't have to change.
Here is my code so far.
def list_users(self):
with open("usercheck.txt", "r") as ulst:
self.usr_list = []
for line in ulst:
self.usr_list.append(line)
def online(self):
for self.name in self.usr_list:
self.onlbl = tk.Label(self, text = self.name,bg = "#42f480")
self.onlbl.grid(row = self.onlcnt,column = 5,padx = 0)
self.onlcnt +=1
Running the online function with after just creates duplicates of the same label and does not update the amount of labels. self.onlcnt is 0
The reason might be that you are using the self keyword in your loop variable, which causes the variable to stay constant: don’t.
def online(self):
for name in self.usr_list:
self.onlbl = tk.Label(self, text = name,bg = "#42f480")
self.onlbl.grid(row = self.onlcnt,column = 5,padx = 0)
self.onlcnt +=1
Also you might want to store the Labels in a list, so you can access them later:
def online(self):
try:
self.labels
except AttributeError:
self.labels = []
self.onlcnt = 0
for name in self.usr_list:
onlbl = tk.Label(self, text = name,bg = "#42f480")
onlbl.grid(row = self.onlcnt,column = 5,padx = 0)
self.labels.append(onlbl)
self.onlcnt +=1
root.after(5000, self.online) #run it again

Multiple ComboBoxes in Tkinter Python

I'm trying to generate multiple ComboBoxes with values from a "config.ini" file, the config.ini file data is:
priority1 = Normal:farty-blobble-fx.wav:2
priority8 = Reclamacao:buzzy-blop.wav:3
priority3 = Critico:farty-blobble-fx.wav:5
priority2 = Urgente:echo-blip-thing.wav:4
and the goal is turning the sound files names to the select values in the comboboxes.
My code to generate the comboboxes is:
content_data = []
for name, value in parser.items(section_name):
if name=="name":
self.note.add(self.tab2, text = value)
else:
data_prior = value.split(":")
self.PRIOR_LABEL = Label(self.tab2, text=data_prior[0])
self.PRIOR_LABEL.grid(row=data_prior[2],column=0,pady=(10, 2),padx=(40,0))
self.PRIOR_SOUNDS = None
self.PRIOR_SOUNDS = None
self.box_value = StringVar()
self.PRIOR_SOUNDS = Combobox(self.tab2, textvariable=self.box_value,state='readonly',width=35)
self.PRIOR_SOUNDS['values'] = getSoundsName()
self.PRIOR_SOUNDS.current(int(getSoundsName().index(data_prior[1])))
self.PRIOR_SOUNDS.grid(row=data_prior[2],column=1,pady=(10, 2),padx=(30,0))
self.PLAY = Button(self.tab2)
self.PLAY["width"] = 5
self.PLAY["text"] = "Play"
self.PLAY["command"] = lambda:playSound(self.PRIOR_SOUNDS.get())
self.PLAY.grid(row=data_prior[2], column=3,pady=(10,2),padx=(5,0))
And i was unable to show the current values of the "config.ini" file in the comboboxes.
Thank you in advance.
The problem is that you're creating more than one combobox, yet you keep overwriting the variables in each iteration of the loop. At the end of the loop, self.PRIOR_SOUNDS will always point to the last combobox that you created. The same is true for self.box_value, self.PLAY, etc.
The simplest solution is to use an array or dictionary to store all of your variables. A dictionary lets you reference each widget or variable by name; using a list lets you reference them by their ordinal position.
A solution using a dictionary would look something like this:
self.combo_var = {}
self.combo = {}
for name, value in parser.items(section_name):
...
self.combo_var[name] = StringVar()
self.combo[name] = Combobox(..., textvariable = self.combo_var[name])
...

Categories

Resources