1

I'm creating a quiz in Python that you can edit, but one of the sections needs the questions to be saved in a file. In this section of code, I'm trying to read the file and then put what it has read into a list so that I can pull certain questions to be displayed on certain buttons. But I keep getting errors that say the list isn't callable.

I've tried to use strip to strip the string of the quotes that appear but I get an error saying

"list" object has no attribute "strip"

I'm relatively new to Python so I'm struggling to find other fixes, I've tried to isolate the print in a different line then put it into the list but its ended up saying the list isn't callable again.

textquestions=open('listofquestions','r')

allthequestions = textquestions.readlines()

forbuttons=[print(allthequestions)]

textquestions.close

q1 = forbuttons(0)

buttonq1 = tk.Button(self, text=forbuttons[q1][0])
buttonq1.pack()

The file has this written in it and nothing else (filler questions to get it to work first):

["What would you wear to work?","Hoodie", "Suit", "Shorts","2"],["How would you greet a customer?","Hey", "Hi", "Hello", "0"],["How many years of experience do you have?","Loads", "None", "Some","1"],["Why do you want to work here?","It's fun", "No money", "Friend told me to","2"]

Yet when they are printed they seem to be printing like this:

'["What would you wear to work?","Hoodie", "Suit", "Shorts","2"],["How would you greet a customer?","Hey", "Hi", "Hello", "0"],["How many years of experience do you have?","Loads", "None", "Some","1"],["Why do you want to work here?","It's fun", "No money", "Friend told me to","2"]'

And I think the quotes around all of it are stopping the list from working? Although I'm not sure, maybe the rest of the code is not right either.

finefoot
  • 9,914
  • 7
  • 59
  • 102
  • 1
    `forbuttons=[print(allthequestions)]` this makes no sense. – sashaaero Jan 05 '19 at 13:25
  • I don't get what you are trying to achieve. Do you want a separate button for each question in the file? And where these questions should be shown when the user click on the button? On the terminal? In a popup dialog? – Valentino Jan 05 '19 at 13:29

3 Answers3

3

The error

"list" object has no attribute "strip"

appears, because - like the error says - you tried to call method strip on an object of type list. You meant to call strip on a string inside a list I suppose. Anyways, strip doesn't help you with your problem, I think.

In general: You should probably change the way you're saving your question data.


However, that being said, you can still load the data with:

import yaml

with open('listofquestions') as f:
    data = f.read()
    questions = yaml.safe_load('[{}]'.format(data))

What happens is, that the content of your file can be interpreted as a partial YAML file - the only thing missing is an opening bracket [ and a closing bracket ]. We can add those to the content after reading the file and parse as YAML content afterwards.

Improvements:

The first question is in questions[0][0], the three answers in questions[0][1], questions[0][2] and questions[0][3] and the correct solution in questions[0][4].

You can access these values to build your GUI similar to this:

screenshot

import tkinter as tk
root = tk.Tk()
label_question = tk.Label(root, text=questions[0][0])
label_question.pack(side=tk.TOP)
button_answer_1 = tk.Button(root, text=questions[0][1])
button_answer_1.pack(side=tk.LEFT)
button_answer_2 = tk.Button(root, text=questions[0][2])
button_answer_2.pack(side=tk.LEFT)
button_answer_3 = tk.Button(root, text=questions[0][3])
button_answer_3.pack(side=tk.LEFT)
root.mainloop()
finefoot
  • 9,914
  • 7
  • 59
  • 102
2

You need to parse the string read from the text file so that Python sees its contents in a structured fashion.

One very simple way to do this is to add [ and ] and treat it as a JSON structure:

import json

with open('listofquestions') as textquestions:
    questions_json = '[' + textquestions.read() + ']'
    allthequestions = json.loads(questions_json)

Python' eval function would work as well, but it can introduce security vulnerabilities if the input data is untrusted (it will execute arbitrary code in it).

Florian Weimer
  • 32,022
  • 3
  • 48
  • 92
2

There are several problems with your approach, some coding errors, some conceptual.

The problem with the concept:

As I observed, the main issue is that you seem to think that putting some text into a file that is a comma-separated sequence of square-bracket enclosed comma-separated double-quoted text elements will create a list in your Python program when read using the file's handle with readlines().

What's more, when you complained about the single-quote enclosed string that was printed, it seems like you expected what you read from the file to be printed as separate square-bracket enclosed elements (perhaps each on a single line?), instead of a single string.

First, what you have in your text read from the file are not Python lists. Python source code is only interpreted as source code, with statements and data like numbers, lists, tuples, etc. because they are read by the Python interpreter.

When you open() a file and then call readlines() on the file handle, it is not the Python interpreter that reads the file. Actually, at a low level, it is the kernel that reads the file in appropriately sized chunks and puts it into memory, namely into the piece of memory your allthequestions variable refers to. Think about it: if the first theory were true, then using readlines() on a file would actually execute anything that looked like a Python command. Then, how would it even know what to return? The result of the last expression, perhaps? A list of all expression results in the file? A tuple of them? No matter how we look at it, it would be hard to define what even your expected behavior for readlines() is. Things like this are best experimented upon in isolated, smaller examples, before putting them to use in a complete program.

Also, as its name suggests, readlines() reads text as lines, not as comma-separated Python lists or any other tokens. The result, although not a list, is an iterable collection of all lines in the file. This means that in your case - according to what you have written -, is a single line, containing:

["What would you wear to work?","Hoodie", "Suit", "Shorts","2"],["How would you greet a customer?","Hey", "Hi", "Hello", "0"],["How many years of experience do you have?","Loads", "None", "Some","1"],["Why do you want to work here?","It's fun", "No money", "Friend told me to","2"]

, including the newline at the end.

Inspecting this data structure, it seems like you want to parse a single sequence of sequences from a file, where each nested sequence contains text elements. The first (or in other words, 0th) element of each nested sequence is a question, whereas every other text element is a possible answer to that question. What the number in the last element means, I could not decipher.

Solutions:

You could use a text parsing method called "regular expressions", but it might be too complicated for what you are trying to achieve. Instead, what I'd suggest is to choose a different data structure.

If you'd only like to handle your data in Python, I'd suggest a method of serialization, like the pickle module. This module allows you to write a python object to a file in such a format that it can be read back again into a variable (of course, NOT with readlines(), which is for reading lines, not objects).

To demonstrate how it works at the writing and reading end, here are some code snippets:

Example for the writing component:

# write_questions.py
import pickle

all_the_questions = (["What would you wear to work?","Hoodie", "Suit",     "Shorts","2"],["How would you greet a customer?","Hey", "Hi", "Hello", "0"],["How     many years of experience do you have?","Loads", "None", "Some","1"],["Why do you     want to work here?","It's fun", "No money", "Friend told me to","2"])

questions_file = open("questions.dmp", "w")
pickle.dump(all_the_questions, questions_file)

questions_file.close()

Example for the reading component:

# read_questions.py
import pickle

questions_file = open("questions.dmp")

all_the_questions = pickle.load(questions_file)

print(all_the_questions[0])
print(all_the_questions[1])

The data structure of all_the_questions was a tuple of lists of strings, which was dumped into the file "questions.dmp" using pickle.dump(). Note that the created file, "questions.dmp" contains a special object notation that is used by pickle to read its contents back as a variable, and is not human-readable!

Once you've run the reading part, you'll see that indexing on the all_the_questions variable worked properly after the variable was created from the file contents using pickle.load() and you get something like this written to your terminal:

['What would you wear to work?', 'Hoodie', 'Suit', 'Shorts', '2']
['How would you greet a customer?', 'Hey', 'Hi', 'Hello', '0']

Another solution would be to use a standard textual data format, like CSV (comma separated values), for which there are parsers in the Python standard library. Basically, you could store each question list as a separate line in a "questions.csv" file without delimiting it with [] characters, and each string could occupy a position between commas within the question line, like this:

What would you wear to work?,Hoodie, Suit, Shorts,2
How would you greet a customer?,Hey, Hi, Hello, 0
How many years of experience do you have?,Loads, None, Some,1
Why do you want to work here?,It's fun, No money, Friend told me to,2

The answer is already too long, so I leave it up to the reader interested in using CSV parsing to look up how it works. There are several resources available, just as for pickle. The benefits are that the data, when stored in a file, will be human-readable, and handling it won't be restricted to Python programs, since CSV is a fairly often used, old format.

Coding issues:

The previous section already touched on the coding part with regards to readlines(), but there are some more problems.

First:

forbuttons=[print(allthequestions)]

This line prints the text representation of the collection object stored in allthequestions, converts the return value of print into a list, and binds the resulting value to forbuttons. Since print() always returns a value of None, the value of forbuttons will be a singleton list, only containing None. Printed, this would look something like this:

[None]

Next, the

textquestions.close

line does not actually call the close() function on the file handle, it only evaluates the function object, doing nothing.

Then, the original snippet tries to call the object in forbuttons with an argument of 0 as if it were a function. Alas, it is not, it is the list [None], as explained above:

q1 = forbuttons(0)

Afterwards, you suddenly decide to treat forbuttons as an indexable list instead of a function, a multi-dimensional one at that!

buttonq1 = tk.Button(self, text=forbuttons[q1][0])

To be honest, I really have no idea what this would get you, so I can not propose an alternative either. Maybe, what you wanted to do was something like:

buttonq1 = tk.Button(self, text=allthequestions[0][0])

, where the button's text would be set to the very first question, had you used e.g. pickle instead of readlines().

Another issue I find in your data representation is that you basically use magic numbers like 0 to remember where the actual question part of a question list is. Maybe using a dictionary where the key is the actual question string, and the value the possible answers would be better. In that case, a single question structure would look like:

{ "What would you wear to work?" : ["Hoodie", "Suit", "Shorts","2"] }

The naming convention is also whacky for a couple of reasons:

First, it is ambiuous what is what. Is a "question" a whole data structure that includes the question string as it would be stated by a human, as well as the possible answers? Or is it just the question string?

Second: It does not look like variables in general follow any sane naming conventions. Usual naming conventions include "camelCase", "PascalCase" (or upper camel case), "snake_case", etc. The Python style guide, PEP-8 recommends using "snake_case" for variables and functions, "UPPER_SNAKE_CASE" for variables intended to be constant and "PascalCase" for type names.

P.S.

In general, one should always learn and know what each concept, function, etc. is about / does before applying them, especially together. This requires a lot of practice and experimentation, as well as learning from documentation.

To be honest, the original question does not look at all like much research or work was put into it. Despite this, I have not disliked it, seeing that the original poster was a new contributor, and - as it seems - fairly new to programming as well.

Larry
  • 427
  • 2
  • 10