Instructions
Python’s design philosophy emphasizes simplicity, readability, and ease of use, making it an accessible language for both beginners and experienced developers. The Python community has adopted this philosophy for their code, and as a Python programmer, you’re encouraged to do the same. In this section, you’ll learn general guidelines and best practices to follow when writing your own Python programs by improving a couple of small Python apps.
The Zen Of Python And Pythonic Code
There’s an “Easter egg” built into Python. To see it, Open the checklist-starter.ipynb notebook. Enter the following into a new code cell and run it:
# Run me!
import this
Rather than import a library called this, which doesn’t exist, this command causes Python to print The Zen of Python by Tim Peters. This poem, written by one of the most important contributors to the Python language, captures the philosophy and spirit of the Python programming language. It’s a good idea to keep the ideas expressed in The Zen of Python in mind while coding in Python.
As you spend more time working with Python, you will probably encounter the term Pythonic. It refers to code that follows conventions, generally accepted best practices and idioms that have grown around Python. Pythonic code reflects the principles of The Zen of Python by following its aphorisms, embracing the design of the Python language, and taking full advantage of its features.
Keep The Zen of Python in mind when working on the exercises in this demo, where you’ll make some code more Pythonic.
PEP 8
The Zen of Python is a good read, but if you want more concrete guidance on writing Python code, you should consult the document known throughout the Python community as PEP 8.
PEP is short for Python Enhancement Proposal, a design document that provides information to the Python community or proposes a new Python feature. PEP 8, the eighth such document to be released, is a set of guidelines and best practices for writing Python code. It’s considered the de facto standard for Python code style.
Refactoring An App To Make It More Pythonic
It’s time to take a working app written in Python and refactor its code. Refactoring better utilizes Python’s features and follows the guidelines and practices of the Python community.
The app is a checklist app based on the ChecklistItem class example from the previous section. The code works — you won’t be fixing errors, but making it more Pythonic.
Using Brackets To Make Long Lines Readable
Find the code cell stating with the comment # Initial checklist. It contains a line that defines checklist, a list of ChecklistItem instances representing the contents of the checklist.
While the line is technically correct, it’s long and goes against The Zen of Python’s line, “Readability counts.”
Fortunately, Python knows that something that begins with some opening bracket — (, [, or { — will eventually end with a closing bracket — ), ], or }. You can use this to break up a long line, or in this case, a long list.
Fix the code in the cell by reformatting it to read like this:
# Initial checklist
checklist = [
ChecklistItem("Clean the living room"),
ChecklistItem("Walk the dog", True, "high"),
ChecklistItem("Buy groceries"),
ChecklistItem("Make dinner", priority="high")
]
Putting A Comma After The Last Item In A List, Tuple, Set, Or Dictionary Literal
Add one more item to the end of checklist:
ChecklistItem("Fix toaster")
See if your change worked. Run the cell defining checklist, then check checklist’s contents by entering checklist into a new code cell and running it.
There’s a good chance that you were presented with a SyntaxError message that ended with Perhaps you forgot a comma?. If you saw this message, you forgot to add a comma after the previous list element before adding a new one.
This is why it’s become standard practice to put a comma after the end of every element of a list, tuple, set, or dictionary; it prevents this kind of mistake.
Make sure that every element in checklist has a comma after it, including the last one.
Using The __repr__() Method When Defining Classes
Confirm that the changes you made work by entering checklist into a new code cell and running it. If you get an error make sure you have run all the previous cells before running the newly created one. This will display the list’s contents, with each Checklistitem instance in the list represented by the output of the __repr__() method. Remember, __repr__() returns the developer-facing string representation of an instance.
Go to the Checklistitem cell, comment out the __repr__() method, and run the cell. Run the # Initial checklist cell and then, enter checklist into a new code cell and run it. Without a __repr__() method, Python displays each ChecklistItem instance using its internal format, which is less readable and makes debugging harder.
This is why it’s important to include a __repr__() method in your classes.
Before you continue, return to the Checklistitem cell, and re-activate the __repr__() method by uncommenting it and running the cell. Finally, run the # Initial checklist cell.
Using Python’s Ternary Operator
Look at ChecklistItem’s __str__() method, which returns the user-facing string representation of an instance. It sets a variable named checkbox to a checked box emoji if the checklist item it represents is checked or a gray box emoji representing an unchecked box if it’s unchecked.
You can streamline __str__()’s code by using a form of if…else that works like the ternary operator (?:) in languages that borrow their syntax from C.
Update __str__() to the following, then run the cell:
def __str__(self):
"""Return a user-friendly string representation of the item."""
return f"{"✅" if self.checked else "⬜️"} {self.name} {self.priority_emoji()}({self.priority})"
Note that this version uses less code and eliminates the need for the checkbox variable.
Confirm that your changes work by running the # Initial checklist cell, then in a new code cell, enter print(checklist[1]), and run it.
Using Truthy and Falsy Values To Make Code Concise
Go to the code cell where the display_checklist() function is defined. The function works, but it could be improved.
The if statement checks to see if checklist is empty by checking its length. If you invert the logic to check if it’s not empty, you can use the fact that a non-empty list is truthy (evaluates as True) and an empty list is falsy (evaluates as False).
With that fact in mind, update display_checklist() to check if checklist is not empty like this:
# Show the user the checklist
def display_checklist(checklist):
"""Show the user the checklist."""
if checklist:
item_number = 1
for item in checklist:
print(f"{item_number}: {item}")
item_number += 1
else:
print("The checklist is empty.")
Run the cell. Enter display_checklist(checklist) in a new code cell and run it. You’ll see the checklist’s contents.
Using enumerate() When Iterating To Get Both Index And Item
display_checklist() still has room for improvement. If checklist isn’t empty, the current code uses a for loop to iterate through the list items, and it also sets up the index variable to store the number of the item currently being printed. The index variable is incremented at the end of each iteration.
Python’s enumerate() function can make the code more concise. It returns an iterator that returns two-value tuples where the first value is the index and the second value is the corresponding element from the sequence.
Better still, enumerate() has an optional start parameter that lets you specify the starting index. The default is 0, but when producing output for users, it’s often helpful to use a start index of 1.
Update display_checklist() to use enumerate() to iterate through checklist and display its contents:
# Show the user the checklist
def display_checklist(checklist):
"""Show the user the checklist."""
if checklist:
for index, item in enumerate(checklist, start=1):
print(f"{index}: {item}")
else:
print("The checklist is empty.")
Run the display_checklist() code cell. Re-run the display_checklist(checklist)cell. You’ll see the checklist’s contents again.
Maybe You Need An in, Not An or
Find the code cell where the add_item_to_checklist() function is defined. Note that after it gets the user’s input about the item’s priority, it performs three if comparisons joined by or operators to see if the user entered low, medium, or high. This would get unwieldy if there were more valid options.
Fortunately, there’s an alternative. You can test to see if the user’s input matches any element in a list of valid options using the in operator.
Update add_item_to_checklist() with the code below and run the cell:
def add_item_to_checklist(checklist):
"""
Get an item name and priority from the user
and add it to the checklist.
"""
while True:
name = input("What's the item's name?").strip()
if name:
break
print("Please enter a name for the item.")
while True:
priority = input("What's its priority (low, medium, or high)?").strip().lower()
if priority in ["low", "medium", "high"]:
break
else:
print("Please enter 'low', 'medium', or 'high'.")
new_item = ChecklistItem(name, False, priority)
checklist.append(new_item)
Test the updated function by running the line add_item_to_checklist(checklist) in a new code cell and then entering an item name and priority. Then confirm that the item is in the list by running display_checklist(checklist).
You Can Chain Comparisons
Go to the code cell containing the edit_item_in_checklist() function. After the line that asks the user which item they want to edit, there’s an if that compares the value of index to confirm that it’s between 0 as a lower bound and len(checklist) as an upper bound. Whenever you see this kind of comparison, chain them.
Update the edit_item_in_checklist() function as shown below, then run its cell:
def edit_item_in_checklist(checklist):
if not checklist:
print("There are no items in the checklist. There's nothing to edit.")
return
print("Here are the items:")
display_checklist(checklist)
index = int(input("Which item do you want to edit?")) - 1
if 0 <= index < len(checklist):
while True:
name = input("What's the item's name?").strip()
if name:
break
print("Please enter a name for the item.")
while True:
priority = input("What do you want to change the priority to (low, medium, or high)?")
if priority in ["low", "medium", "high"]:
break
print("Please enter 'low', 'medium', or 'high'.")
checklist[index].name = name
checklist[index].priority = priority
Test the updated function by running the line edit_item_in_checklist(checklist) in a new code cell and then entering a revised item name and priority. Then confirm that the item has been edited by running display_checklist(checklist).
Using f-strings Instead Of String Concatenation
Find the code cell where the delete_item_from_checklist() function is defined. Here’s the line in that function that builds the string asking the user if they’re sure they want to delete an item:
question = "Are you sure you want to delete " + checklist[index].name + "?"
The same thing happens at the end of the function when the user is informed that the item they selected for deletion has been deleted.
While concatenation works, reading and maintaining strings built using interpolation with f-strings is easier. Update the function to the following and run the cell:
def delete_item_from_checklist(checklist):
"""
Ask the user to select a checklist item,
then delete it if they're sure.
"""
if not checklist:
print("There are no items in the checklist. There's nothing to edit.")
return
print("Here are the items:")
display_checklist(checklist)
index = int(input("Which item do you want to delete?")) - 1
if 0 <= index < len(checklist):
question = f"Are you sure you want to delete {checklist[index].name}?"
answer = input(question).strip().lower()
if answer.lower() in ["y", "yes", "ok", "okey dokey"]:
deleted_item = checklist.pop(index)
print(f"Deleted {deleted_item.name}.")
Dictionaries Can Be Decision Makers
Run the cells containing check_item() and uncheck_item(). Scroll past them and find the cell containing main(), the app’s main function.
It presents the user with a menu of options, asks them to input a number corresponding to the option they want, and then uses an if…elif…else statement to execute the appropriate function. It works, but there’s a way to make the code more compact, readable, and maintainable.
Since functions are first-class objects in Python, they can be assigned to variables or as values in data structures. You can replace lengthy if…elif…else statements with a dictionary where the keys select the function to execute, and the corresponding values are function names.
Update the section of main() starting with the # Act on the user’s selection comment to the following:
# Act on the user’s selection
ACTIONS = {
1: display_checklist,
2: check_item,
3: uncheck_item,
4: add_item_to_checklist,
5: edit_item_in_checklist,
6: delete_item_from_checklist,
}
print("\n")
if 1 <= choice <= 6:
ACTIONS[choice](checklist)
elif choice == 7:
print("Checklist main() finished.")
break
else:
print("Please enter a valid choice (1 - 7).")
print("\n")
If the user enters 1 through 6, the app uses the ACTIONS dictionary, selecting the function using the user’s input as the key and then executing that function. The possible selections and associated functions look almost like a table, and they’re easier to read than a chain if…elif…else option.
Run the main() cell. Add a new cell and run the main() function. Try all the options to confirm that the app works after all the changes you’ve made.
If you’ve reached this point, congratulations! You’ve successfully refactored a Python app and made it more Pythonic!