6 Ways To Power Up Your Python Code

Actionable tips for polishing your parseltongue

Tom Noble
Better Programming

--

Snake wearing party hat
Photo by wrathletbirds on Imgur.

As software developers, we strive to write code that works well. This, of course, requires code that works at all but stresses the importance of maintainability and clarity. Thankfully, developers are not paid by line of code, allowing us to focus on what really matters in our profession: creating simple and elegant solutions to complex problems.

Python is a language that makes it very easy to do this when used right. I think its popularity among programming newcomers is a testament to this fact. Readable code is approachable for those looking to learn the fundamentals.

On the other hand, Python isn’t a particularly strict teacher, making it easy to reinforce bad habits. This is fine while we are still learning the ropes and function takes priority over form — we must learn to walk before we can run, after all. Nevertheless, we owe it to our fellow developers and future selves to keep our code clean. Why write five lines when you can write one? Readability counts.

This article is aimed mainly at those unfamiliar with Python best practices and shorthand syntax. The steps described are small and simple enough that they can — and should — be applied quickly and easily to any Python project immediately after reading this.

Let’s dive in!

1. Use List Comprehensions

Operating on lists is to Python as slithering is to a snake. That is to say, very common! Below is an example in which we wish to select all names beginning with B, converting them to lowercase in the process:

names = ["Alice", "Brian", "Betty", "Bob", "Christa"]
b_names = []
for name in names:
if name.startswith("B")
b_names.append(name.lower())

This code certainly gets the job done, but it’s verbose. Iterating, filtering, and transforming collections of data is so routine, in fact, that Python provides us with some neat syntax for achieving all three in a single statement:

list = [expression for item in iterable if condition]

A list comprehension allows us to build a new list from an existing one by iterating over its items, applying an expression to those for which some condition is True.

names = ["Alice", "Brian", "Betty", "Bob", "Christa"]
b_names = [name.lower() for name in names if name.startswith("B")]

Voila!

2. Use Sequence Unpacking To Extract Related Data

When operating on compound types, we will inevitably need to reach inside to fetch data. A customer’s details, for instance:

person = ("Bob", 22, "4 Adder Avenue")
name = person[0]
age = person[1]
address = person[2]

Sequence unpacking allows us to do this in a much cleaner way by assigning successive elements in a collection to multiple variables at once:

person = ("Bob", 22, "4 Adder Avenue")
name, age, address = person

Granted, this example is relatively simple. More often, we will be working with data types of arbitrary structure: a pandas DataFrame, a Pillow Image, or a PyQt Widget perhaps.

A tidy way of applying this syntax in more complex scenarios is to write a function encapsulating type-specific fetching logic, using multiple return values to package up related data as a tuple. This allows us to neatly unpack back into individual variables when the function is called.

def get_user_info(user):
...
return name, age, address
user = get_active_user()
name, age, address = get_user_info(user)

3. Use Dictionary Comprehensions

With the previous tips in mind, we can introduce dictionary comprehensions to our code:

other = {key': value' for key, value in dict.items() if condition}

The items() method returns a collection of key-value pairs that we can unpack and process to build a new one. As with a list comprehension, we can filter dictionary items by an optional condition. Those remaining are transformed by two expressions (key’ and value’) to produce our final items.

This syntax works wonders for selecting sub-attributes from a nested dictionary:

4. Prefer Creation Over Deletion

Data is usually updated over the course of a program (e.g. removing underachieving students from a school’s records). When the requirements are presented as such, the following code may seem most natural:

In Python, however, it’s often cleaner to build new structures for the data we want rather than deleting those we don’t. Notice that removing those who underachieved is really the same as keeping those who have achieved well, which is feasible in fewer lines using our mastery of comprehension syntax:

students = {"Alice": 90, "Bob": 81, "Charlie": 42}
achievers = {
name: score for name, score in students.items() if score >= 60
}

This approach also makes it easier to test and reason about our code by guaranteeing that a variable holds the same value no matter when and where it is used. Fully immutable state is a whole other can of snakes, though.

5. Use if-else Assignment Where Possible

This one is short and sweet. Say we want to assign a variable based on the value of a boolean expression. Using Python’s ternary operators, we can perform if-else assignment, turning a lengthy five-liner:

mood = ""
if is_sunny(today):
mood = "Happy"
else:
mood = "Sad"

Into a slick one-liner:

mood = "Happy" if is_sunny(today) else "Sad"

Note that this only works if we have a value for both cases. We couldn’t use the following code to initialise a variable only when our condition is True:

mood = "Happy" if is_sunny(today) # Results in a SyntaxError

6. Assign Related Context Managers Together

Python’s context managers give us control over the lifetime of temporary resources, such as file streams. Commonly, multiple managers must be maintained at once (e.g. when combining data from several files). This can lead to overly nested code:

with open(file1) as in1:
with open(file2) as in2:
with open(file3) as out:
out.write(in1.read() + in2.read())

A lesser-known trick is that Python allows more than one manager to be assigned at once, allowing our code to be flatter:

with open(file1) as in1, open(file2) as in2, open(file3) as out:
out.write(in1.read() + in2.read())

Lengthy lines can be hard to follow. Therefore, we would be wise to use this trick sparingly. Keeping context managers around only as long as we really need them is even better:

with open(file1) as in1, open(file2) as in2:
contents = in1.read() + in2.read()
with open(file3) as out:
out.write(contents)

--

--

Tom Noble
Better Programming

Software Engineer & Robotics Enthusiast writing about Technology, Mathematics, Mental Health, and anything else I find interesting.