by Al Sweigart
Saving Variables with the shelve Module
You can save variables in your Python programs to binary shelf files using the shelve module. This way, your program can restore data to variables from the hard drive. The shelve module will let you add Save and Open features to your program. For example, if you ran a program and entered some configuration settings, you could save those settings to a shelf file and then have the program load them the next time it is run.
Enter the following into the interactive shell:
>>> import shelve >>> shelfFile = shelve.open('mydata') >>> cats = ['Zophie', 'Pooka', 'Simon'] >>> shelfFile['cats'] = cats >>> shelfFile.close()
To read and write data using the shelve module, you first import shelve. Call shelve.open() and pass it a filename, and then store the returned shelf value in a variable. You can make changes to the shelf value as if it were a dictionary. When you’re done, call close() on the shelf value. Here, our shelf value is stored in shelfFile. We create a list cats and write shelfFile['cats'] = cats to store the list in shelfFile as a value associated with the key 'cats' (like in a dictionary). Then we call close() on shelfFile.
After running the previous code on Windows, you will see three new files in the current working directory: mydata.bak, mydata.dat, and mydata.dir. On OS X, only a single mydata.db file will be created.
These binary files contain the data you stored in your shelf. The format of these binary files is not important; you only need to know what the shelve module does, not how it does it. The module frees you from worrying about how to store your program’s data to a file.
Your programs can use the shelve module to later reopen and retrieve the data from these shelf files. Shelf values don’t have to be opened in read or write mode—they can do both once opened. Enter the following into the interactive shell:
>>> shelfFile = shelve.open('mydata') >>> type(shelfFile)
Here, we open the shelf files to check that our data was stored correctly. Entering shelfFile['cats'] returns the same list that we stored earlier, so we know that the list is correctly stored, and we call close().
Just like dictionaries, shelf values have keys() and values() methods that will return list-like values of the keys and values in the shelf. Since these methods return list-like values instead of true lists, you should pass them to the list() function to get them in list form. Enter the following into the interactive shell:
>>> shelfFile = shelve.open('mydata') >>> list(shelfFile.keys()) ['cats'] >>> list(shelfFile.values()) [['Zophie', 'Pooka', 'Simon']] >>> shelfFile.close()
Plaintext is useful for creating files that you’ll read in a text editor such as Notepad or TextEdit, but if you want to save data from your Python programs, use the shelve module.
Saving Variables with the pprint.pformat() Function
Recall from Pretty Printing that the pprint.pprint() function will “pretty print” the contents of a list or dictionary to the screen, while the pprint.pformat() function will return this same text as a string instead of printing it. Not only is this string formatted to be easy to read, but it is also syntactically correct Python code. Say you have a dictionary stored in a variable and you want to save this variable and its contents for future use. Using pprint.pformat() will give you a string that you can write to .py file. This file will be your very own module that you can import whenever you want to use the variable stored in it.
For example, enter the following into the interactive shell:
>>> import pprint >>> cats = [{'name': 'Zophie', 'desc': 'chubby'}, {'name': 'Pooka', 'desc': 'fluffy'}] >>> pprint.pformat(cats) "[{'desc': 'chubby', 'name': 'Zophie'}, {'desc': 'fluffy', 'name': 'Pooka'}]" >>> fileObj = open('myCats.py', 'w') >>> fileObj.write('cats = ' + pprint.pformat(cats) + 'n') 83 >>> fileObj.close()
Here, we import pprint to let us use pprint.pformat(). We have a list of dictionaries, stored in a variable cats. To keep the list in cats available even after we close the shell, we use pprint.pformat() to return it as a string. Once we have the data in cats as a string, it’s easy to write the string to a file, which we’ll call myCats.py.
The modules that an import statement imports are themselves just Python scripts. When the string from pprint.pformat() is saved to a .py file, the file is a module that can be imported just like any other.
And since Python scripts are themselves just text files with the .py file extension, your Python programs can even generate other Python programs. You can then import these files into scripts.
>>> import myCats >>> myCats.cats [{'name': 'Zophie', 'desc': 'chubby'}, {'name': 'Pooka', 'desc': 'fluffy'}] >>> myCats.cats[0] {'name': 'Zophie', 'desc': 'chubby'} >>> myCats.cats[0]['name'] 'Zophie'
The benefit of creating a .py file (as opposed to saving variables with the shelve module) is that because it is a text file, the contents of the file can be read and modified by anyone with a simple text editor. For most applications, however, saving data using the shelve module is the preferred way to save variables to a file. Only basic data types such as integers, floats, strings, lists, and dictionaries can be written to a file as simple text. File objects, for example, cannot be encoded as text.
Project: Generating Random Quiz Files
Say you’re a geography teacher with 35 students in your class and you want to give a pop quiz on US state capitals. Alas, your class has a few bad eggs in it, and you can’t trust the students not to cheat. You’d like to randomize the order of questions so that each quiz is unique, making it impossible for anyone to crib answers from anyone else. Of course, doing this by hand would be a lengthy and boring affair. Fortunately, you know some Python.
Here is what the program does:
Creates 35 different quizzes.
Creates 50 multiple-choice questions for each quiz, in random order.
Provides the correct answer and three random wrong answers for each question, in random order.
Writes the quizzes to 35 text files.
Writes the answer keys to 35 text files.
This means the code will need to do the following:
Store the states and their capitals in a dictionary.
Call open(), write(), and close() for the quiz and answer key text files.
Use random.shuffle() to randomize the order of the questions and multiple-choice options.
Step 1: Store the Quiz Data in a Dictionary
The first step is to create a skeleton script and fill it with your quiz data. Create a file named randomQuizGenerator.py, and make it look like the following:
#! python3 # randomQuizGenerator.py - Creates quizzes with questions and answers in # random order, along with the answer key. ➊ import random # The quiz data. Keys are states and values are their capitals. ➋ capitals = {'Alabama': 'Montgomery', 'Alaska': 'Juneau', 'Arizona': 'Phoenix', 'Arkansas': 'Little Rock', 'California': 'Sacramento', 'Colorado': 'Denver', 'Connecticut': 'Hartford', 'Delaware': 'Dover', 'Florida': 'Tallahassee', 'Georgia': 'Atlanta', 'Hawaii': 'Honolulu', 'Idaho': 'Boise', 'Illinois': 'Springfield', 'Indiana': 'Indianapolis', 'Iowa': 'Des Moines', 'Kansas': 'Topeka', 'Kentucky': 'Frankfort', 'Louisiana': 'Baton Rouge', 'Maine': 'Augusta', 'Maryland': 'Annapolis', 'Massachusetts': 'Boston', 'Michigan': 'Lansing', 'Minnesota': 'Saint Paul', 'Mississippi': 'Jackson', 'Missouri': 'Jefferson City', 'Montana': 'Helena', 'Nebraska': 'Lincoln', 'Nevada': 'Carson City', 'New Hampshire': 'Concord', 'New Jersey': 'Trenton', 'New Mexico': 'Santa Fe', 'New York': 'Albany', 'North Carolina': 'Raleigh', 'North Dakota': 'Bismarck', 'Ohio': 'Columbus', 'Oklahoma': 'Oklahoma City', 'Oregon': 'Salem', 'Pennsylvania': 'Harrisburg', 'Rhode Island': 'Providence', 'South Carolina': 'Columbia', 'South Dakota': 'Pierre', 'Tennessee': 'Nashville', 'Texas': 'Austin', 'Utah': 'Salt Lake City', 'Vermont': 'Montpelier', 'Virginia': 'Richmond', 'Washington': 'Olympia', 'West Virginia': 'Charleston', 'Wisconsin': 'Madison', 'Wyoming': 'Cheyenne'} # Generate 35 quiz files. ➌ for quizNum in range(35): # TODO: Create the
quiz and answer key files. # TODO: Write out the header for the quiz. # TODO: Shuffle the order of the states. # TODO: Loop through all 50 states, making a question for each.
Since this program will be randomly ordering the questions and answers, you’ll need to import the random module ➊ to make use of its functions. The capitals variable ➋ contains a dictionary with US states as keys and their capitals as values. And since you want to create 35 quizzes, the code that actually generates the quiz and answer key files (marked with TODO comments for now) will go inside a for loop that loops 35 times ➌. (This number can be changed to generate any number of quiz files.)
Step 2: Create the Quiz File and Shuffle the Question Order
Now it’s time to start filling in those TODOs.
The code in the loop will be repeated 35 times—once for each quiz—so you have to worry about only one quiz at a time within the loop. First you’ll create the actual quiz file. It needs to have a unique filename and should also have some kind of standard header in it, with places for the student to fill in a name, date, and class period. Then you’ll need to get a list of states in randomized order, which can be used later to create the questions and answers for the quiz.
Add the following lines of code to randomQuizGenerator.py:
#! python3 # randomQuizGenerator.py - Creates quizzes with questions and answers in # random order, along with the answer key. --snip-- # Generate 35 quiz files. for quizNum in range(35): # Create the quiz and answer key files. ➊ quizFile = open('capitalsquiz%s.txt' % (quizNum + 1), 'w') ➋ answerKeyFile = open('capitalsquiz_answers%s.txt' % (quizNum + 1), 'w') # Write out the header for the quiz. ➌ quizFile.write('Name:nnDate:nnPeriod:nn') quizFile.write((' ' * 20) + 'State Capitals Quiz (Form %s)' % (quizNum + 1)) quizFile.write('nn') # Shuffle the order of the states. states = list(capitals.keys()) ➍ random.shuffle(states) # TODO: Loop through all 50 states, making a question for each.
The filenames for the quizzes will be capitalsquiz
The write() statements at ➌ create a quiz header for the student to fill out. Finally, a randomized list of US states is created with the help of the random.shuffle() function ➍, which randomly reorders the values in any list that is passed to it.
Step 3: Create the Answer Options
Now you need to generate the answer options for each question, which will be multiple choice from A to D. You’ll need to create another for loop—this one to generate the content for each of the 50 questions on the quiz. Then there will be a third for loop nested inside to generate the multiple-choice options for each question. Make your code look like the following:
#! python3 # randomQuizGenerator.py - Creates quizzes with questions and answers in # random order, along with the answer key. --snip-- # Loop through all 50 states, making a question for each. for questionNum in range(50): # Get right and wrong answers. ➊ correctAnswer = capitals[states[questionNum]] ➋ wrongAnswers = list(capitals.values()) ➌ del wrongAnswers[wrongAnswers.index(correctAnswer)] ➍ wrongAnswers = random.sample(wrongAnswers, 3) ➎ answerOptions = wrongAnswers + [correctAnswer] ➏ random.shuffle(answerOptions) # TODO: Write the question and answer options to the quiz file. # TODO: Write the answer key to a file.
The correct answer is easy to get—it’s stored as a value in the capitals dictionary ➊. This loop will loop through the states in the shuffled states list, from states[0] to states[49], find each state in capitals, and store that state’s corresponding capital in correctAnswer.
The list of possible wrong answers is trickier. You can get it by duplicating all the values in the capitals dictionary ➋, deleting the correct answer ➌, and selecting three random values from this list ➍. The random.sample() function makes it easy to do this selection. Its first argument is the list you want to select from; the second argument is the number of values you want to select. The full list of answer options is the combination of these three wrong answers with the correct answers ➎. Finally, the answers need to be randomized ➏ so that the correct response isn’t always choice D.
Step 4: Write Content to the Quiz and Answer Key Files
All that is left is to write the question to the quiz file and the answer to the answer key file. Make your code look like the following:
#! python3 # randomQuizGenerator.py - Creates quizzes with questions and answers in # random order, along with the answer key. --snip-- # Loop through all 50 states, making a question for each. for questionNum in range(50): --snip-- # Write the question and the answer options to the quiz file. quizFile.write('%s. What is the capital of %s?n' % (questionNum + 1, states[questionNum])) ➊ for i in range(4): ➋ quizFile.write(' %s. %sn' % ('ABCD'[i], answerOptions[i])) quizFile.write('n') # Write the answer key to a file. ➌ answerKeyFile.write('%s. %sn' % (questionNum + 1, 'ABCD'[ answerOptions.index(correctAnswer)])) quizFile.close() answerKeyFile.close()
A for loop that goes through integers 0 to 3 will write the answer options in the answerOptions list ➊. The expression 'ABCD'[i] at ➋ treats the string 'ABCD' as an array and will evaluate to 'A','B', 'C', and then 'D' on each respective iteration through the loop.
In the final line ➌, the expression answerOptions.index(correctAnswer) will find the integer index of the correct answer in the randomly ordered answer options, and 'ABCD'[answerOptions.index(correctAnswer)] will evaluate to the correct answer’s letter to be written to the answer key file.
After you run the program, this is how your capitalsquiz1.txt file will look, though of course your questions and answer options may be different from those shown here, depending on the outcome of your random.shuffle() calls:
Name: Date: Period: State Capitals Quiz (Form 1) 1. What is the capital of West Virginia? A. Hartford B. Santa Fe C. Harrisburg D. Charleston 2. What is the capital of Colorado? A. Raleigh B. Harrisburg C. Denver D. Lincoln --snip--
The corresponding capitalsquiz_answers1.txt text file will look like this:
1. D 2. C 3. A 4. C --snip--
Project: Multiclipboard
Say you have the boring task of filling out many forms in a web page or software with several text fields. The clipboard saves you from typing the same text over and over again. But only one thing can be on the clipboard at a time. If you have several different pieces of text that you need to copy and paste, you have to keep highlighting and copying the same few things over and over again.
You can write a Python program to keep track of multiple pieces of text. This “multiclipboard” will be named mcb.pyw (since “mcb” is shorter to type than “multiclipboard”). The .pyw extension means that Python won’t show a Terminal window when it runs this program. (See Appendix B for more details.)
The program will save each piece of clipboard text under a keyword. For example, when you run py mcb.pyw save spam, the current contents of the clipboard will be saved with the keyword spam. This text can later be loaded to the clipboard again by running py mcb.pyw spam. And if the user forgets what keywords they have, they can run py mcb.pyw list to copy a list of all keywords to the clipboard.
Here’s what the program does:
The command line argument for the keyword is checked.
If the argument is save, then the clipboard contents are saved to the keyword.
If the argument is list, then all the keywords are copied to the clipboard.
Otherwise, the text for the keyword is copied to the keyboard.
This means the code will need to do the following:
Read the command line ar
guments from sys.argv.
Read and write to the clipboard.
Save and load to a shelf file.
If you use Windows, you can easily run this script from the Run... window by creating a batch file named mcb.bat with the following content:
@pyw.exe C:Python34mcb.pyw %*
Step 1: Comments and Shelf Setup
Let’s start by making a skeleton script with some comments and basic setup. Make your code look like the following:
#! python3 # mcb.pyw - Saves and loads pieces of text to the clipboard. ➊ # Usage: py.exe mcb.pyw save
It’s common practice to put general usage information in comments at the top of the file ➊. If you ever forget how to run your script, you can always look at these comments for a reminder. Then you import your modules ➋. Copying and pasting will require the pyperclip module, and reading the command line arguments will require the sys module. The shelve module will also come in handy: Whenever the user wants to save a new piece of clipboard text, you’ll save it to a shelf file. Then, when the user wants to paste the text back to their clipboard, you’ll open the shelf file and load it back into your program. The shelf file will be named with the prefix mcb ➌.