Intro to Programming: Files

In this article, I'll start exploring files, and how to work with them programmatically. Specifically, I'll talk about what files are, what memory is, and how to NOT crash your computer.
By Ciprian Stratulat • Updated on Jun 20, 2022


Welcome back to the last article in my Intro to Programming series. In this article, I'll start exploring files and how to work with them programmatically. Specifically, we'll talk about what files are, what memory is, and how to NOT crash your computer.



Disadvantages of Using input()

So far, we have only discussed one way to get input data into our programs: reading it from users using the input() function. That, of course, works for small amounts of data, but it has certain disadvantages. First of all, people get tired of entering large amounts of data by answering questions. In addition, what happens if the user has to answer 20 questions, but realizes that they made a mistake on question 18? They have to restart the program and go through all of those questions again. Using the input() method is slow and error-prone. Also, more importantly, input data is not saved. If you restart the program and you want to run it with the same input, or just slightly different input, you have to go through all the questions again.

So, really, you should only use the input() function to get one-off input data into your program. You would not load a store inventory using questions and answers because that would take forever and, if your program crashed, you'd have to do it all over again. But you could use the input() function to ask a user for his or her name, for example.

If you need to get larger, more structured data into your program, a better solution is to use files. 


What Are the Advantages of Using Files in Python

Files have a few advantages: they have specific formats, which can be understood by many programs. For example, if your accountant uses a Microsoft Excel file, that same Excel file can also be opened in Google Docs, and can also be opened by a Python program. The format of the file is well documented, so many programs can use it. Files are also very portable - meaning, you can send a friend a file, perhaps by sharing it via Dropbox or emailing it. Also, files can be stored long-term, either on your local hard drive or somewhere in the cloud.


File System

All modern computer systems, as you're well aware, use files to organize data. Because of this, all computers use something called a file system, which is really just some computer code that's responsible for controlling how data is stored into files and how it's retrieved from files. Without files and the file system, all the data on our computers would be one giant data soup, and it would be impossible to tell where one thing ends and another begins.

We won't go into details about the file system - I'm just going over these terms because I think it's important to have a mental model for these concepts, even if it's a simple one.


Files are Stored on Hard Drives

Fundamentally, what you need to remember is that files are pieces of data organized in a specific format, which are generally stored on a computer hard drive (can be either your computer's hard drive or some hard drive in the cloud). The hard drive is like the human long-term memory. It saves things to be used in the future.


Most Computer Programs Run in RAM

However, you also need to be aware that computer programs run inside what we call the RAM, which is short for random access memory. The RAM is the computer's short-term memory. If things in RAM don't get saved to the hard drive, they won't be remembered. It's the same with humans. We can store some things in our short-term memory, but if we don't commit it to long-term memory, we won't be able to remember it. With computers, RAM typically gets cleared, for example, when the computer restarts. That's how you lose unsaved work: when you make some changes to a file, but you forget to save it, those changes only exist in RAM. Then, when your computer crashes and you restart it, you won't find it in the file because it wasn't saved in the long-term memory.


Ram is Super Fast, But Small

I'm simplifying things a bit here, but that's the general gist of it. And the reason why it's important to be aware of this distinction between RAM and hard drives is what I mentioned earlier: computer programs generally run in RAM. They do that because RAM is super fast. But RAM is also small. Much faster than hard drives, much smaller than hard drives too. 

And this is the really important thing to remember: when your program opens a file and reads it, it loads parts, or all of it, in RAM.

So, what can happen here? If your program is trying to open a huge file, it will have to basically copy all of it, or at least parts of it, from long-term memory to short-term memory. From hard drive to RAM. If the data that gets copied in the process is huge, it may end up taking up all your RAM. And that's how your computer can become very slow (because there's just not enough free RAM left for other programs to run properly), or can even crash.

So, basically, this is why very large files can be an issue when using them in your programs.

This was just an introduction, but it's important to keep these details in mind. They will come in handy at some point in the future. Next, we're going to talk about the ways in which Python allows us to interact with files.


How to Open a File in Python

The first thing we're going to look at is how to open a file. To do that, we use the open function. The open function, in its simplest form, takes just a single input.


That is the string representing the path of the file we're trying to open. 

When it comes to the file path, you need to be aware of one important distinction between Windows and Mac or Linux machines.

More specifically, when you write Windows paths as strings, you need to use two backslashes, not just one. So, you can see here that the first two examples, which are correct, both use two backslashes, not just one. The reason for this is that, if you recall from the lessons on strings, one backslash followed by a letter is used to represent special characters. 

For example, in the first incorrect example here, the '\n' is treated by Python as a newline character. You can get around that by using two backslashes.

More generally, whenever you want to actually represent a backslash character inside a Python string, you need to write two backslashes.

Mac and Linux paths are a bit simpler because they actually use forward slashes, not backslashes. Here are some examples of paths pointing at files on a Mac or Linux machine. It's important to be aware of the different ways that file paths are represented on different systems.


Why It's a Bad Idea to Hard-Code File Paths

One other thing I want to point out is that, generally, it's a bad idea to hard-code file paths in your code. Why is that? Well, because when we write computer code, we generally want to make sure that it runs on as many other computers as possible. That is, we want to make sure our programs are portable, that we can take them with us to a different computer. We already know that we can take files with us to a different computer, so it makes sense that we want to make sure our code also runs on different computers. 

The problem is that if you include a specific path, like this one, if I go to a computer that doesn't have a user named Ciprian, the program will crash. That's because when it tries to open the file at that specific path, it won't be able to find it.

So what should you do instead? There are actually several alternative ways, but, for now, the simplest ones are these two. The first option is to prompt the user to specify the path that your program needs. For example, you can use the input function to ask the user to tell you where on their computer the file named names.txt is located.

The second option is to copy the file to the same folder where your program is running. So, if you're running a Jupyter notebook, you can copy it to the same folder where the Jupyter notebook is located. If you have a script that you wrote using a code editor like Atom or Sublime, you can copy the files you need to the same folder where the script is saved. 

And that works because of this simple trick: if the file you need, say names.txt, is located in the same folder as the program that needs it, instead of specifying the whole path, you can simply type open('names.txt'). When Python sees that, it assumes that the file called names.txt is in the same folder as the program you wrote, so it doesn't actually need to know the whole path to the file because it can just look inside the folder to find it.

If you wanted to open these files, you'd do something like this. If you're running your program on a Windows computer, you'd use the first version of the file path. If you're running your program on a Mac or Linux computer, you'd use the second version of the path.

So, what happens when we run these commands? Basically, when you execute the open function, what it returns is not yet the contents of the file, but rather an object that represents the file. What is an object? At a high level, objects are just data structures, which are used to programmatically represent some real-world concept. And the important thing about objects is that they have methods attached to them. A list, if you recall, is an object. It represents the real-world concept of a list, and we've seen that it has some methods attached to it. One example of a list method is the append method, which can be used to add new elements to a list.

Similarly, when we run the open function, what we get back is an object that represents the real-world concept of a file. We basically get some data that represents the object. That object does not include the contents of the file by default. Can you guess why that is so? That's because there are actually multiple ways to read the content and to represent it. More on that in just a moment.


So, after we run this line of code, my_file is a variable that stores some data that represents the file we want to work with. It's a bit like how the floorplan of a house is not the house itself, but rather just a representation of a house. The floorplan still gives you important information, like what's the area of the house, how many floors it has, where are the fire exits, etc.

This file object actually has methods attached that allow us to read the contents of the file. The read() method reads the file and returns a string that has the whole file content. The readlines() method reads the file and returns a list where each item is a line inside the file. We'll see these in action shortly.

Next up, we'll look at how to write data to a file programmatically. 


How to Write to a File in Python

We saw how we can read files programmatically. But how do we write to files? 

To learn that, we have to briefly revisit the 'open' function. Namely, it's time to learn that the open function also takes another form, shown here. Specifically, it has an optional parameter called mode, which allows us to tell Python what we want to do with the file: Do we just want to read from it? Do we want to overwrite it? Do we want to append some lines at the bottom? Do we want to both read and write? We make the distinction between these different scenarios by setting the file open mode parameter.


What Are the Different Modes to Open a File in?

Why are there multiple modes of opening a file to begin with? Why not allow both reading and writing by default and not worry about it? Well, that has to do with permissions. Sometimes, you need to write code that only reads a file. Maybe it's a super important file and you want to make sure that your program doesn't accidentally overwrite it. Or, similarly, maybe you want to make sure that, when someone else uses your program, they can only append to a file, but they don't have permission to read the file, for security reasons. Python supports all those scenarios by allowing you to specifically indicate how you want the file in question to be used.

The mode parameter can take these values. If no value is specified, as we've seen so far, the file is opened as read-only. That means that our program can read it, but can't write to it. The same thing happens if we do specify the mode, but we set it to the string r, which is short for read. In both of these cases, if the file doesn't exist, we'll get an error.

If we set the mode to the string r+, the file will be opened for both reading and appending. However, by default, appending will be done in the beginning of the file in this mode. Also, if you use this mode and the file does not exist, you'll get an error.

If we specify the mode and we set it to the string w, short for write, the file will be created if it doesn't exist. If it does exist, its contents will be deleted. This mode allows us to replace an old file with a completely new file, but it doesn't allow us to read the file.

If we set the mode to the string w+, the file will be opened for overwriting and for reading. That means that, if the file doesn't exist, it will get created, and if it does exist, it will get overwritten. The file will also be available for reading.

If we set the mode to the string a, short for append, the file will be opened for appending. That means that we won't be able to read the file, but we'll be able to add new content at the end. If we use this mode and we specify a path for a file that doesn't exist, a blank file will first be created.

Finally, if we set the mode to the string a+, the file will be opened for both reading and appending at the end.

Now, you don't have to memorize these. If you forget what each mode does, you can always check the documentation, or do a Google search to find which mode you should use to achieve what you want. In the beginning, a good mode to default to is a+ because it allows you to read the file and, if you write new content to the file, the content gets added at the end. This mode probably has the fewest surprises when you use it.

Now, with this information, we can use the open function to open a file and write to it. We do that by using code that looks like what you can see here. We simply specify the mode as a string, right after the path. In this case, we're using the a+ mode.

Just like before, the open function returns a file object, which is just a representation of the file we want to work with. And, in addition to read() and readlines(), this file object also has methods for facilitating writing to the file. Namely, you can use the write() function to write some string to the file, and you can use writelines() to write each of the items of a list. So write() always needs a string as an input and writelines() needs a list of strings as an input.


How to Close a File

Finally, before we quickly see all of this in action, I want to make you aware of a very important thing: if you use the open function to open a file, you absolutely need to use the close function to close the file once you're done with it.

If you don't do that, you can run into some issues. The reason is this: when you use the open() function, you basically tell the file system that you're using that file. The file system says: "Ah, ok, you're using it, so I'm not going to allow other programs to change it while you use it." If you forget to close the file when you're done with it, and your program's still running, other programs won't be permitted access to the file, even though you no longer need it. So let's say you write a text file, but you forget to close it in your program. If you want to open it in say, Notepad or Atom, there's a chance you won't be able to do that because you didn't close it.

You might have already experienced this behavior before, actually. If you ever had a file opened in, say, Microsoft Word, but forgot that you have it opened and instead tried to delete it, you probably got an error telling you that the file can't be deleted because Microsoft Word is using it. That's the File System being nice, and coordinating file access for us. So, just remember to close the file.

But hey, remembering to use the close() function when we're done with a file is kind of annoying, isn't it? We're only human, what if we just forget? Luckily, the creators of the Python programming language are also only human, so they thought about it. To make file closing easier, they built the with clause.

At the top here, you can see how we've been using the open function so far. We basically execute it, and we assign the file object that it returns to a variable. Then, we use that file object to, for example, read all the lines from the file and store them as a string inside a variable called file_content. Then we print the file_content variable, which, essentially, prints the contents of the file we just opened. And then, once we're all done with the file, we run the close() method to close the file.

The bottom is a simpler way to do that. We use the with keyword, followed by the same execution of the open() function, and then we use the as keyword to assign the result to a variable called my_file. This line achieves the exact same thing as the open function line above: it executes the open() function and stores a file object representing the file we want to work with in the variable called my_file.

Also, notice the colon here at the end. We need that because what follows below is an indented block of code. We already know what this means: we use the 4 spaces or 1 tab indentation to tell Python that the code below is to be considered as part of the with clause. So similarly, inside the with close, we read all the lines in our file and store them as a string inside a variable called file_content. Then, we print file_content.

Notice what's missing here? It's the close function. How come? Well, what happens is that, once the with clause is finished, Python automatically closes the file for us. So, rather than having to remember to call the close() function ourselves, all we need to do is use the with clause to wrap all the code that works with a file. 

Here, you can see that, by the time we get to the print('hello') line, the file has already been closed. The print('hello') line is NOT inside the with statement because it doesn't have the indentation.

This is quite a bit, so feel free to take a moment and reread parts of the concepts that you don't fully understand yet. Next, we will dive in and write some code that works with files. 


How to Open and Read a File

Let's get hands-on with files, and play with opening and reading files using Python. 

I've already created a file called names.txt, and I've placed it in the same directory as my Jupyter notebook. By the way, if you're unsure of where your Jupyter notebook is located, you can just type pwd inside a new cell and run it.

# We can type pwd into a cell

# and Jupyter will return the current path that the notebook is saved on.

Ok, so let's see what's inside my names file. I can just type my_file = open('names.txt') print( I don't need to specify a path because I know that this file is in the same folder as my Jupyter notebook. I'm also not specifying a mode, because I'm only reading from the file for now, and the default mode is r. On the second line, I'm reading the content of the file using the read method, and printing it - all at the same time. Remember that when you have nested functions like these, the inner-most one gets executed first, and the result of it gets passed to the outer one. So, in this case, will be executed first, and the result of that will be passed as input to the print() function, which will be executed second. 

# Let's run and print our file
my_file = open('names.txt')

# Our output is:
# Sylvester
# Bugs Bunny
# Tom
# Jerry

Cool, looks like names.txt has a bunch of cartoon character names.

Now, I want to show you something that can be a little tricky to grasp at first, so listen closely. Notice that, since we haven't yet closed the file, we technically still have the file object available to us. What happens if we try to run the read() method again? Let's try it. Hm, strange, looks like now, print( returns nothing. Why is that? To understand this result, it helps if you imagine a cursor. When you open the file, the cursor is at the beginning of the file. But once you've read the whole file, you basically scrolled all the way to the end of the file. So, what happens if you now try to read again, while you're at the end of the file? Well, nothing - you're at the end of the file, so there's nothing left to read.

# If we try to run the read() function a second time:
# nothing is returned

That's exactly what's happening here. After we call the method read() the first time, we've basically read the whole file and scrolled all the way to the end. So now, when we call it again, we get nothing because our cursor is at the end of the file, and there's nothing left to read from that point on.


The Seek Method

But, what if you absolutely need to read it again? In that case, you would need to "scroll up", or seek the beginning of the file again. Or, of course, you could close the file and reopen it again, but that would be unnecessary and kind of annoying. Luckily, there's a better way to move to the beginning of a file again. And that's the seek method.

If we just type, we can now type shift+tab inside the Jupyter notebook, and we see this helpful message telling us how to use the method. By the way, you'll need to press shift and tab twice in order to get the extended version of the help message. So here, we can see that seek() can be used to change our current position in the file. We can pass the integer 0 to seek() in order to move back to the beginning of the file. Let's do that. So, we'll change this code to and run it. Now, if we run print(, we get the whole file content again.

# Let's use the seek function to go back to the beginning of our file

# Now when we print(
# we get the same output as before:
# Sylvester
# Bugs Bunny
# Tom
# Jerry

Perfect. Now, we can close the file using my_file.close(). Once the file is closed, we can no longer read from it. So, if we try to run again, we get an error telling us that we're attempting an "I/O" operation on a closed file, which is not possible. "I/O" stands for input/output, or basically read/write. You'll come across this term quite often, so try to remember it.

# When we close our file
# and then try to read it again
# we get the following error:
#ValueError Traceback (most recent call last)
#<ipython-input-7-2b1e0f30a114> in <module>
#----> 1
#ValueError: I/O operation on closed file.

Let's now open the file using the with clause and, instead of reading the whole file in a string, let's use the readlines() method. So let's write: with open('names.txt') as my_file: lines = my_file.readlines() for line in lines: print(line + "---"). See if you can figure out what this code does.

Basically, we open the file called names.txt and store the resulting file object in a variable called my_file. And this is just a variable name, so you can use whatever other name you want here. 

Inside the with clause, we now read all the lines from the file using the readlines() method. Remember that readlines() returns a list of strings, and that each item in that list represents one line in the file. So, what can we do with this list? Well, lists are sequences of items, so they are iterable. This means that we can go through the list one by one, and do something with each item. In this case, we're iterating over the elements in the list, that is to say, we are going through each of the lines in the file, one at a time, and for each of them, we print the line, followed by three dashes. If we run this, we can see the result.

# Let's copy our file and use .readlines()
# After each line, we'll add --- 
with open('names.txt') as my_file:
    lines = my_file.readlines()
    for line in lines:
        print(line + "---")

# Our output is the following:
# Sylvester
# ---
# Bugs Bunny
# ---
# Tom
# ---
# Jerry
# ---

This is a nice example that uses several things that we've learned: we open a file, read the contents in a list, and iterate through that list. Actually, you'll see this pattern quite often as you code more. Let's do something more useful with it. What if we want to read a file and print all the lines in uppercase?

Simple, right? You may remember from strings that we can use the upper() method to change a string to uppercase. So, we can just write: with open('names.txt') as my_file: lines = my_file.readlines() for line in lines: print(line.upper()). Just like before, we basically open the file names.txt for reading, and we store the file object in a variable called my_file. Then, we use that variable to get all the lines from the file, and we store them in another variable, called lines. So, the variable lines stores a list. Then, we can iterate through that list, and for each item in the list, we can just print it in uppercase.

# Let's iterate through our file and uppercase every character
with open('names.txt') as my_file:
    lines = my_file.readlines()
    for line in lines:

# Our output is now

Now, why is there an extra line between the names? To understand that, you need to remember that each line in a file ends in a newline character. We typically don't think of the newline as a special character in day-to-day life. When we're typing something in a text editor, the newline just kinda happens automagically when we press Enter. But Python doesn't really work like that. When it comes to text, it only works with strings. And if a string has multiple lines, it needs a way to indicate where a new line starts. It achieves that by using the special newline character, which we've encountered before. It's the \n. For example, if we printprint('hello\nmy name is bugs bunny'), we see that after the word hello, a new line is created and the text my name is bugs bunny appears on that new line. How did Python know that it should do that? Simple. After the word hello, we wrote that \n character. By the way, you can't avoid using that character if you want to write something on a new line. If you try to run the code:

      my name is bugs bunny')

you'll actually get an error because you're basically breaking up a single line of code incorrectly, which is not valid Python syntax.

Ok, so back to our list of uppercase names. Each line in the file basically ends in a newline, which is this \n character. In addition, when we use the print method to print something on the screen, it too adds a newline after the string it needs to print. So that's the equivalent of running print('hello\n\nmy name is bugs bunny'). There are now two newline characters, between the text hello and the text my name is bugs bunny. That's exactly what's happening with our uppercase names too, and why we have an extra line between them.

Next, we'll look at how and where we can write in a file programmatically. 


How to Open and Write to a File

So far we've only talked about reading files. Let's see a few examples of writing to files. First, let's append another cartoon character name to our file. Let's say Popeye. We can do that simply: with open('names.txt', 'a') as my_file: my_file.write('Popeye'). Notice that, this time, I had to specify the mode I wanted, which is a for append. If we now read the file, so with open('names.txt') as my_file: print(, we see that we have Popeye at the end.

# Let's use append to add Popeye to our list:
with open('names.txt', 'a') as my_file:

#Now when we read our file again:
with open('names.txt') as my_file:
# we see that Popeye was appended
#Bugs Bunny

Let me also quickly show you what happens if we try an action that's not permitted. For example, let's say that we open our names.txt file for appending, but we try to read from it. So, we try to run with open('names.txt', 'a') as my_file: print( and we get an error. Specifically, we get an UnsupportedOperation exception, with the message not supported. This is trying to tell us that we've attempted some operation on the file - in this case reading from it - which is not supported, because we opened the file for appending only.

# If we try to read our file after opening it with a:
with open('names.txt', 'a') as my_file:
# We get an error message
#UnsupportedOperation Traceback (most recent call last)
#<ipython-input-15-c24f1912960b> in <module>
#1 with open('names.txt', 'a') as my_file:
#----> 2 print(
#UnsupportedOperation: not readable

Let's go over one more example, this time a bit more complex. Oftentimes, what you need to do with files is read them, and then change them in some manner. For example, say someone asks us to write a program that reads a file containing names of cartoon characters, and wants us to change the file such that we add the names in uppercase at the end of the file.

No problem. Whenever you hear that you have to modify existing content inside a file, you know that you'll need to be able to both read the file and write to it. In this case, we're trying to read the content of the file, and then append some new content at the end. So, it looks like the mode we want is r+. Let's write the code: with open('names.txt', 'r+') as my_file: lines = my_file.readlines() for line in lines: my_file.write(line.upper()).

To summarize, we opened the file, we read all the lines, and we stored them in a list variable called lines. Each line is now a string inside the lines list. Then, we iterate through each of those lines, and we append the uppercase version of it to the end of the file.

If we now open the file and read it, so, with open('names.txt') as my_file: print(, we see that we have both the lowercase and the uppercase versions of the names.

# Let's append a list with uppercase characters to our original list
# We'll need to read and write, so let's use r+
with open('names.txt', 'r+') as my_file:
    lines = my_file.readlines()
    for line in lines:

# Now we can print the list and see the updates
with open('names.txt') as my_file:
# Our output is now
#Bugs Bunny

What would have happened if, instead of r+, we used w+? Let's try it. We can just copy-paste the code above and just replace r+ with w+. And now again we read the file, so we can copy-paste that part of the code too. When we run it, we get nothing. Why is that? Well, remember that the modes that start with w, so both w and w+ work this way: they first open the file, then they delete any existing content from the file, and only then do they execute whatever code we want to execute. So, in our case, Python sees that we're using the w+ mode to open names.txt, so it first deletes the contents of the file, and only then starts executing our code. Then, the first line of code inside the with clause attempts to read all the lines in the file. That's allowed, because w+ mode also supports reading. But! There are no lines in the file anymore, because the content has already been deleted. So, that line's variable contains an empty list - a list with no elements. Because the list has no elements, nothing gets uppercased, and nothing gets written, so we end up with an empty file. Not what we expected.

The various modes to open a file can be a bit tricky to fully understand at first. So, before you go and use them on important files that you would not like to lose forever, make sure you understand the differences between them by using them with toy-files that you make yourself, like the names.txt here.



That's it for the basics of working with files in Python. Knowing how to read and write files programmatically is very powerful, because much of the digital world is organized in files. Here, we only looked at plain text files, but there are Python packages out there that are specialized for working with CSV files, Excel files, images etc. And, best of all, the basic principles that we saw here will still apply, even in those cases.

As usual, the key to mastering these concepts is practice, so keep playing with Python, and keep solving problems with code. 

It has been a pleasure to be your guide through Edlitera's Introduction to Programming series. I hope you enjoyed it.

If you understood all that we discussed in these articles, you are well on your way to becoming a programmer and enjoying the wealth of opportunities that come with it.

If you are still having trouble with some of the concepts, please don't give up. It may take some time, but programming is not an impossible task. Reread the articles, search the internet for alternative explanations and, more importantly, keep writing code. It doesn't matter how silly or simple this code may seem at first - it will help you get better.

As you grow as a programmer, you will realize that programming is not just something you do. It becomes part of you because it changes how you think. It is an extremely rewarding activity, both financially and intellectually.

Programming is a way of thinking and a way of doing. The specific language that you use is not even that important, so don't get too discouraged if you hate some programming language or another. In fact, programming interviews nowadays rarely expect you to remember all the details of a specific language. What matters is how you think. And, if you master programming in one programming language, you will be able to pick up new languages very quickly - much, much quicker than your first language.

If you're looking for next steps, my first and strongest advice, of course, is to keep writing code. Also - very importantly - to read a lot of code. You can find lots of open source code on sites like Reading code will show you how other programmers think, how they structure their code, how they go about solving specific problems - and you can learn a lot from this. Apart from writing a lot of code and reading a lot of code, I also encourage you to take our more advanced courses, which will build on this introductory course to further enhance your skills.

Thank you again for reading, and good luck!

Ciprian Stratulat

CTO | Software Engineer

Ciprian Stratulat

Ciprian is a software engineer and the CTO of Edlitera. As an instructor, Ciprian is a big believer in first building an intuition about a new topic, and then mastering it through guided deliberate practice.

Before Edlitera, Ciprian worked as a Software Engineer in finance, biotech, genomics and e-book publishing. Ciprian holds a degree in Computer Science from Harvard University.