Practical Python Programming Fundamentals
Idiomatic Python Syntax
Writing Pythonic Code
Knowing the syntax of a language is one thing. Speaking it fluently and naturally is another. In programming, this fluency is called writing “idiomatic” code. It means using the language's features as they were intended, resulting in code that's not just functional, but also clean, efficient, and easy for other Python developers to understand.
The concept of "Pythonic" code emphasizes writing programs that align with Python's design principles.
Since you already know the basics of loops and variables, we'll focus on these more idiomatic patterns. They're shortcuts and conventions that transform clunky code into elegant solutions.
List Comprehensions
You've probably written for loops to create a new list based on an existing one. It's a common pattern: initialize an empty list, loop over a sequence, and append a new value on each iteration.
squares = []
for i in range(10):
squares.append(i * i)
# squares is now [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
This works, but it's verbose. Python offers a more compact and readable way to do the same thing: a An.
squares = [i * i for i in range(10)]
# This one line produces the exact same list.
The structure reads like a sentence: "make a new list with the expression i * i for each i in range(10)." You can even add a conditional to filter elements.
# Get the squares of only the even numbers
even_squares = [i * i for i in range(10) if i % 2 == 0]
# even_squares is [0, 4, 16, 36, 64]
Style and Truthiness
Code is read more often than it is written. Because of this, consistency matters. Python has a style guide called that outlines conventions for writing readable code. This covers everything from how to name variables (snake_case) to the number of spaces to use for indentation (four). While you don't need to memorize it, using an autoformatter or linter in your code editor will help you stick to it automatically.
One of PEP 8's core tenets is that readability counts. Clear, consistent code is easier to debug and maintain.
Another Pythonic concept is “truthiness.” In conditional statements, you don't always need an explicit comparison. Python has a built-in idea of what is “truthy” (evaluates to true) and what is “falsy” (evaluates to false).
| Falsy Values | Truthy Values |
|---|---|
None | Any non-empty sequence |
False | Any non-zero number |
Zero of any numeric type ( 0, 0.0 ) | True |
Any empty sequence ( '', [], {} ) | Most objects |
Instead of writing if len(my_list) > 0:, a Python developer would simply write if my_list:. It’s cleaner and achieves the same result.
my_name = ""
# Non-pythonic
if my_name == "":
print("Name is empty.")
# Pythonic
if not my_name:
print("Name is empty.")
Better Looping
Sometimes you need both the index and the value while looping. A common but non-idiomatic way to do this is to loop over the range of the list's length.
letters = ['a', 'b', 'c']
for i in range(len(letters)):
print(f"Index {i}: {letters[i]}")
Python's built-in enumerate function simplifies this pattern. It returns both the index and the item at each iteration.
letters = ['a', 'b', 'c']
for index, letter in enumerate(letters):
print(f"Index {index}: {letter}")
What if you need to loop over two lists at the same time? The zip function pairs up elements from multiple iterables.
students = ['Alice', 'Bob', 'Charlie']
scores = [88, 92, 79]
for student, score in zip(students, scores):
print(f"{student} scored {score}")
Note:
zipstops as soon as one of the lists runs out of items. It won't raise an error if the lists are different lengths.
Managing Resources
When you work with external resources like files or network connections, it's crucial to clean up after yourself. For files, this means always closing them. Forgetting to do so can lead to data corruption or resource leaks.
The manual way involves a try...finally block to ensure the close() method is called, even if errors occur.
f = open('my_file.txt', 'w')
try:
f.write('Hello, world!')
finally:
f.close()
This is another pattern that Python simplifies. The with statement creates a temporary context and guarantees that cleanup code is run when the block is exited, no matter what. Any object that supports this behavior, like a file object, can be used with it.
with open('my_file.txt', 'w') as f:
f.write('Hello, world!')
# The file is automatically closed here
This version is safer and much more readable. You don't have to remember to call f.close(), and the scope of the file handle f is neatly contained within the with block.
Let's test your understanding of these idiomatic patterns.
What is the most idiomatic Python way to create a list of squares for the numbers in an existing list called numbers?
What is the primary purpose of PEP 8?
Adopting these conventions will make your code more efficient, readable, and in line with the expectations of the broader Python community.