Python Jumpstart by Building 10 Apps Transcripts
Chapter: App 4: Journal app and file I/O
Lecture: Text-based File I/O and with
0:00 So it's time to add persistence across running sessions by saving and loading our journal from files.
0:08 Let's start by first saving our journal to a file. So I want to come down here and any time that you work with file I/O,
0:16 you always start with this built in called open. So here we can pass the file name by default you see the mode is R
0:23 that's read only text but we are going to change that to W for write, and we are not really going to worry about the other options.
0:30 So we need a file name that goes here and we want to have the mode to be write, because we are going to create or replace a file,
0:37 we are not going to just read one, and we'll catch this and I call it fout, from file output stream,
0:44 you can call it whatever you like, it's just a variable. The first thing we really have to do to make this work
0:48 is actually get this file name here, so I could say, let's actually have a little folder, that we are going to save our files into... a directory,
0:57 so let's call this journals, let's suppose we want to put them in there, and I could say something like this, I could say filename = './journals/'
1:07 and then there is this name remember we are passing in this journal name here, so let's say name, and let's suppose on the end
1:13 when I have a 'jrl' file extension to indicate our custom journal format or whatever, it's hard it's going to be worthy of that but here we go.
1:21 Now technically, this would work on OS X and Linux, it may not work on Windows, right, remember,
1:28 Windows uses backslashes to separate directories, not for slashes, but more important than that, if I am combining multiple directories,
1:37 say if I am giving a like a base directory equals something like this '~/', and I am giving a relative directory = data/temp.
1:51 If I am given these two and I want to build up the full path, I would say full_file = base_dir + and I need to use a little separator here
2:00 because you can see on this part there is no separator up there and then we'll say + relative directory, like so
2:07 and if this is passed me well do I know whether there is this little slash on the end
2:13 do I need to put it here, do I check, all those little nuance details in Python we don't actually need to deal with any of that
2:20 and we get OS independent path operations, all I am using is this module called OS. So I want to come up here, I want to import OS, and down here,
2:31 I can say instead of doing this thing, I would like this file to be os.path. and we can just say join,
2:38 and put a whole bunch of little paths to segments together, so we can say ./journals, the name, like so.
2:46 Now, it won't really join the extension, right, it will think that's a separate file, so it's up to us to add the extension but other than that,
2:52 it will take an arbitrary number of segments and put them together here and let's just print would load form this here,
3:01 ok, so when I run this, and we exit, you would see what load I suppose that should be saved to, but here we have our ./journals,
3:09 and we can even go one step farther make this very explicit, right, if I am going to write that to a log file I need to know
3:15 what the working folder was, at the time, right, so I could say path.absolutepath, now if I run it, exit out of here,
3:25 you can see user screen caster source Python jumpstart... darara number 4,
3:29 right this is the full path and this works perfectly well in all of the major platforms. Ok?.
3:36 So this is going to let us work with that folder right there, and let's go and say just this, saving to here,
3:45 and put a little indicator that that's not proper app output at the terminal there, all right, so now we have our file name, we can go and open it,
3:56 and we can go through our journal, remember our journal is this list of drinks, say for entry in journal data, and say fout.write
4:06 and I can write this out and say entry, now, if I say this way it's not quite going to work the way we hope,
4:13 and let's go ahead and leave it for a moment, say fout last thing we want to close this
4:19 you always want to close the file handles as soon as you are done with them,
4:22 so let's run this and I'll show you better way to deal with this close scenario. So if I run this and let's add an entry,
4:29 say thing 1 and an entry thing 2, and we exit, you'll see that this was saved and if we go up here there is now this file and if we open it up,
4:39 these are actually all in one line, probably not the best. Now, we probably should talk about the format just a bit anyway,
4:45 we want to keep this super simple, we are going to get to more interesting file formats later, for our journal,
4:51 we are just going to write one entry per line. But notice these are all in the same line so we need to actually add a new line character
4:58 so backslash end and then when we rerun this say let's add a few entries here, and exit, now if we go back to our journal,
5:12 you'll see perfectly misspelled thing 1 and thing 2, excellent. Ok, so it looks like our file I/O is working, now we want to make sure we call close
5:23 if there is some kind of exception or error or early return in this obviously a very small bit of code, but even it could have an error, however,
5:32 the more complicated the code gets the more helpful this what I am about to show is going to be
5:37 if we return without doing this, well that's kind of a problem, right, so what we want to do is in Python
5:43 there is actually this thing called a context manager, that will say for this block or suite of code, I would like this object to exist,
5:51 and regardless of how you leave it through an exception, early return, falling to the bottom, I want you to cleanup, and it's up to the item being used
6:00 to determine what cleanup means, and you can imagine for files that's like close and flesh the buffer.
6:06 So the way we do that is a little different than this, we'll say with, it's kind of reversed,
6:11 we'll say with open file name and same parameters, as fout, and then we do a block like so. And we won't need this anymore,
6:21 because as soon as we leave this block right here, it's defined by the indented section it's going to automatically call close.
6:30 Ok, so let me clean this up a little bit. Here is our final save, we are going to use os.path.absolutepath
6:43 to get a full path from our relative path we built up with join, and in OS we do have these for slashes in here,
6:50 so maybe it would make more sense to write it like so, there we go, that way it doesn't depend on for slash being a particular separator,
6:58 so here now we have a proper OS independent style. We use our context manager, our open method to get the file stream,
7:06 and then we just call write, and we write out the text, if we want the binary version, we would say WB for write binary,
7:12 and that's that, we are going to do something very similar for loading it. In fact, so much so that we are going to use exactly the same line of code,
7:23 I kind of feel like that should be like a method or something, so let's go over here and I could write a method
7:29 in this file or I could go to PyCharm and hit control T and say I'd like to create a method let's call it get full path name something like that,
7:40 and you see down here it's returning this, and we're calling this file name here, let's just do that same thing up there.
7:46 So, this one is different there, usually it will be a file but not always, right, not the first time, so we can say something like this,
7:53 if os.path.exists file name then we want to load it, otherwise we are going to return this thing. Now, we are going to need to initialize that
8:04 and maybe fill the data maybe return the empty version, but let's do this here, so now just like before,
8:10 we are going to use our with, but we are going to not use the w, we are not trying to write it we are just going to read it,
8:15 and I'm going to call it fin for file input, and then we can just say for entry and fin.readlines(),
8:24 and that will just read in the lines and let me just print, would load and put a little print here entry.
8:31 Now this is not going to work out quite as good as we would like, but let me run it, so it would load thing 1 new line, thing 2, new line,
8:40 it turns out that in Python, when you do the read lines the new line character is still stuck on the end, right that back slash end that we out here,
8:50 or just naturally the file, is on here, it's not removed, so we can come over here and say strip(),
8:56 maybe we want to be careful and just say rstrip() off the end, now if I run it, I would say would load thing 1, thing 2,
9:04 those are exactly the entries we were hoping for. Ok, so instead of doing this, over here we say data.apend(entry).
9:12 Now if I could just load it up and I say list, of yeah, I forgot the rstrip(), so let's say rstrip() to remove the white space off the right hand side,
9:22 now have a look, perfect, still misspelled, thing 2, thing 1 exactly the right order because remember, we are reversing it.
9:29 So let's go and actually add a few more, we'll say add this is fun with files, and add one more, today I saved this.
9:40 If we exit out you'll see saving to this location and if we would rerun it, and just do a list, boom, there are things we wrote, back in our file,
9:49 you can see right, there is the chronological order we created which is of course reversed of what we are showing.
9:56 Beautiful, let's go back and just review what we did for load, it's even easier than save, wasn't it, ok, over here in journal we can remove our TODO,
10:04 so we create an empty journal bit of data and then we get the file name using our full path function right, ospath. absolute path and join
10:12 and then, we ask if it does exists let's load it, using our super simple format of one entry per line,
10:19 and then we are going to return either the populated or empty data set and on the way out the door
10:24 we create a writable file and we just write out the format that we were expecting. Perfect.