In the preceding iteration, we added the capability to define multiple beat patterns. However, the beat patterns can be played only on a single script run. When the program is closed and restarted, all previous pattern data is lost.
We need a way to persist or store the beat patterns beyond a single program run. We need the ability to store values in some form of file storage and reload, play, and even edit the patterns. We need some form of object persistence.
Python provides several modules for object persistence. The module that we will use for persistence is called the pickle module. This is a standard library of Python.
An object represented as a string of bytes is called pickle in Python. Pickling , also known as object serialization , lets us convert our object into a string of bytes. The process reconstructing of the object back from the string of bytes is called unpickling or deserialization.
More information about the pickle
module is available at http://docs.python.org/2/library/pickle.html.
Let us illustrate it with a simple example:
import pickle party_menu= ['Bread', 'Salad', 'Bordelaise','Wine', 'Truffles'] pickle.dump(party_menu, open( "mymenu.p", "wb" ) )
First, we serialize or pickle our list PartyMenu
using pickle.dump
and save it in an external file mymenu.p
.
We later retrieve the object using pickle.load
:
import pickle menu= pickle.load( open( "mymenu.p", "rb" ) ) print menu # ['Bread', 'Salad', 'Bordelaise', 'Wine', 'Truffles']
Remember that in our previous iteration, we created a list, called self.pattern_list
, where each item of the list is a dictionary that stores information about one single beat pattern.
If we need to reuse this information, we only need to pickle this self.pattern_list
. Having saved the object, we can later easily unpickle the file to reconstruct our beat patterns.
While we are creating our menu items let us also add an About menu item:
Here, we are particularly interested in saving the project (pickling), and loading the project back (unpickling). The code for menu items is defined in a separate method called create_top_menu
, as shown in the following code (also refer to the code in 3.09.py
):
def create_top_menu(self): self.menubar = Menu(self.root) self.filemenu = Menu(self.menubar, tearoff=0 ) self.filemenu.add_command(label="Load Project",command=self.load_project ) self.filemenu.add_command(label="Save Project",command=self.save_project) self.filemenu.add_separator() self.filemenu.add_command(label="Exit",command=self.exit_app) self.menubar.add_cascade(label="File",menu=self.filemenu) self.aboutmenu = Menu(self.menubar, tearoff=0 ) self.aboutmenu.add_command(label="About",command=self.about) self.menubar.add_cascade(label="About",menu=self.aboutmenu) self.root.config(menu=self.menubar)
The code is self-explanatory. We have created similar menu items in our last two projects. Finally, to display this menu, we call this method from our Main
method.
pickle
module into the current namespace as follows (see the code in 3.09.py
):import pickle
The Save Project menu has a command
callback attached to self.save_project
, which is where we define the pickling process:
def save_project(self): self.record_pattern() #make sure last pattern is recorded file_name = tkFileDialog.asksaveasfilename(filetypes=[('Drum Beat File','*.bt')] , title="Save project as...") pickle.dump(self.pattern_list,open( file_name, "wb" ) "wb" ) ) self.root.title(os.path.basename(filenamefile_name) + " - DrumBeast")
The description of the code is listed as follows:
self.pattern_list
only when the pattern number is changed by the user. In situations where a user might have defined a beat pattern but may not have clicked on the pattern number's Spinbox widget, the pattern is not included in self.pattern_list
. To make sure it is added, we first call self.record_pattern
to capture this beat pattern.save_project
method is called when the user clicks on the Save Project menu, hence, we need to give the user an option to save the project in a file. We have chosen to define a new file extension (.bt
) to keep track of our beat patterns..bt
extension, the data in the self.pattern_list
object is dumped into the file using pickle.dump
.The unpickling process is handled by a method load_project
, which is called from the Load Project menu as follows:
def load_project(self): file_name = tkFileDialog.askopenfilename(filetypes=[('Drum Beat File','*.bt')], title='Load Project') if file_name == '':return self.root.title(os.path.basename(file_name) + " - DrumBeast") fh = open(file_name,"rb") # open the file in reading mode try: while True: # load from the file until EOF is reached self.pattern_list = pickle.load(fh) exceptEOFError: pass fh.close() try: self.Reconstruct_pattern(0,pattern_listself.pattern_list[0]['bpu'],pattern_listself.pattern_list[0]['units']) except: tkMessageBox.showerror("Error","An unexpected erroroccurred trying to reconstruct patterns")
The description of the code is listed as follows:
.bt
extension, the filename is stored in a variable called file_name
.none
because the user cancels the Open File dialog, nothing is done.self.pattern_list
using pickle.load
.self.pattern_list
now contains the list of beat patterns defined in the previous pickle. The file is closed and the first pattern of self.pattern_list
is reconstructed in the drum machine. If there are more than one patterns defined in the serialized file, you can view each of the patterns simply by changing the pattern number Spinbox widget.Pickling, though great for serialization, is vulnerable to malicious or erroneous data. You may want to use pickle only if the data is from a trusted source, or if proper validation mechanisms are in place.
You may also find the json
module useful for serializing objects in JSON and ElementTree
, or
xml.minidom libraries relevant for parsing XML data.
exit
and about
commands:def about(self): tkMessageBox.showinfo("About", "About Info") def exit_app(self): if tkMessageBox.askokcancel("Quit", "Really Quit?"): self.root.destroy()
And add this line to our app
method to override the Close button of the Toplevel window:
self.root.protocol('WM_DELETE_WINDOW', self.exit_app)
This is self-explanatory. We have done similar coding in our previous project.
In this iteration, we used Python's built-in pickle
module to pickle and unpickle the beat patterns defined by the user.
This now lets us save patterns defined by the user. We have also provided the ability to load, replay, and edit the project later.
Now, if you define one or more beat patterns in your program you can save the project with a .bt
file extension. You can later load the project and start working on it from the place where you had last left it.
While we were dealing with the top menu we also completed the code for the About and Exit menu items.
3.142.200.109