- Lab
- A Cloud Guru
Reading and Writing Files with Python
Files are used for many things in programming, including storing and reading data as well as writing to the screen. In this hands-on lab, we'll add a way to read and store information about custom classes, using a file as a flat database for our employee information. To feel comfortable completing this lab, you'll want to know how to read and write to files (watch the "Interacting with Files" video from the Certified Associate in Python Programming Certification course), use class methods (watch the "Custom Constructors, Class Methods, and Decorators" video from the Certified Associate in Python Programming Certification course), and create and use class instances (watch the "Creating and Using Python Classes" video from the Certified Associate in Python Programming Certification course).
Path Info
Table of Contents
-
Challenge
Add `identifier` Attribute to `Employee` Instances and `__init__` Method
Before we start reading our
Employee
data from a file, we're going to add an additional field to help us identify our employees so we can update them later. We're going to call this fieldidentifier
, and we need to add it to the__init__
method as the final parameter with a default value ofNone
. Here's what our method will look like in ouremployee.py
:~/employee.py
class Employee: def __init__(self, name, email_address, title, phone_number=None, identifier=None): self.name = name self.email_address = email_address self.title = title self.phone_number = phone_number self.identifier = identifier def email_signature(self, include_phone=False): signature = f"{self.name} - {self.title}\n{self.email_address}" if include_phone and self.phone_number: signature += f" ({self.phone_number})" return signature
Now we can differentiate instances when reading and writing from a file.
-
Challenge
Add `Employee.get_all` Class Method to Return a List of `Employee` Objects
The first class method we're going to write will read in all of the employees from a file, with each line being a single employee. Here's a list of all the things we need to do:
- Determine the file to read from. If no filename is given, we'll use a default file name of
employee_file.txt
. - Open the file, read each line, split the values on a
,
character, and add an additional value to the list that is the line number of the employees' data. - Create a new
Employee
instance using the data. The data will be stored in the same order as the parameters so we can unpack the data from the file as positional arguments using the*
operator. - Return the list of
Employee
objects.
Here's what this will look like:
~/employee.py
class Employee: default_db_file = "employee_file.txt" @classmethod def get_all(cls, file_name=None): results = [] if not file_name: file_name = cls.default_db_file with open(file_name, "r") as f: lines = [ line.strip("\n").split(",") + [index + 1] for index, line in enumerate(f.readlines()) ] for line in lines: results.append(cls(*line)) return results # remainder of class was unchanged and omitted
Because each line is going to have a
\n
character at the end, we're going to remove that before we split the line into its values. Additionally, when we think of line numbers, we start counting at 1 instead of 0, so we're going to make the identifier begin at 1. - Determine the file to read from. If no filename is given, we'll use a default file name of
-
Challenge
Add `Employee.get_at_line` Class Method to Return a Single `Employee`
The
get_at_line
class method won't be much different thanget_all
, except we want to return a single value. We're going to need to take theline_number
provided as an argument and subtract1
from it so it can be used as an index of the list of lines. Here's one way we could implement this function:class Employee: default_db_file = "employee_file.txt" @classmethod def get_all(cls, file_name=None): results = [] if not file_name: file_name = cls.default_db_file with open(file_name, "r") as f: lines = [ line.strip("\n").split(",") + [index + 1] for index, line in enumerate(f.readlines()) ] for line in lines: results.append(cls(*line)) return results @classmethod def get_at_line(cls, line_number, file_name=None): if not file_name: file_name = cls.default_db_file with open(file_name, 'r') as f: line = f.readlines()[line_number - 1] attrs = line.strip("\n").split(',') + [line_number] return cls(*attrs) # remainder of class was unchanged and omitted
-
Challenge
Add `save` Instance Method to `Employee` Class to Write New Instances to the File
The last method we're going to add is an instance method that will allow us to have an instance update or add its own in the "database" file. This method will need to do a few things. To make it more manageable to see what is going on, we're going to place the logic that builds the line we'll insert into the file into a separate "private" method (starting with a single underscore). Here's what we need this method to do:
- Determine the file to save to, defaulting to the
default_db_file
value if nofile_name
is passed in. - Open the database file in
r+
mode so we don't delete its contents if there are some. - If the instance has an identifier, replace that line by getting all lines in a list and then replacing the appropriate index.
- If the instance does not have an identifier, add the line for this employee to the end of the list.
seek
back to the beginning of the file and usewritelines
to put all the lines back in the file.
Our method to create the line we write into the file will be called
_database_line
. Here are both of the methods:~/employee.py
class Employee: default_db_file = "employee_file.txt" @classmethod def get_all(cls, file_name=None): results = [] if not file_name: file_name = cls.default_db_file with open(file_name, "r") as f: lines = [ line.strip("\n").split(",") + [index + 1] for index, line in enumerate(f.readlines()) ] for line in lines: results.append(cls(*line)) return results @classmethod def get_at_line(cls, line_number, file_name=None): if not file_name: file_name = cls.default_db_file with open(file_name, "r") as f: line = [ line.strip("\n").split(",") + [index + 1] for index, line in enumerate(f.readlines()) ][line_number - 1] return cls(*line) def __init__(self, name, email_address, title, phone_number=None, identifier=None): self.name = name self.email_address = email_address self.title = title self.phone_number = phone_number self.identifier = identifier def email_signature(self, include_phone=False): signature = f"{self.name} - {self.title}\n{self.email_address}" if include_phone and self.phone_number: signature += f" ({self.phone_number})" return signature def save(self, file_name=None): if not file_name: file_name = self.default_db_file with open(file_name, "r+") as f: lines = f.readlines() if self.identifier: lines[self.identifier - 1] = self._database_line() else: lines.append(self._database_line()) f.seek(0) f.writelines(lines) def _database_line(self): return ( ",".join( [self.name, self.email_address, self.title, self.phone_number or ""] ) + "\n" )
We can test our implementation by running
test_employee.py
. If the implementation is correct, we won't see any errors — but if things aren't working correctly, we will see error messages that can hopefully help us.python3.7 test_employee.py
- Determine the file to save to, defaulting to the
What's a lab?
Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.
Provided environment for hands-on practice
We will provide the credentials and environment necessary for you to practice right within your browser.
Guided walkthrough
Follow along with the author’s guided walkthrough and build something new in your provided environment!
Did you know?
On average, you retain 75% more of your learning if you get time for practice.