Powered by

Coding for public service

6 practical tricks every Python developer should have

Python has become a popular programming language because it is clear, versatile, easy to learn, and it has plenty of useful libraries for different tasks. From web development to data science and cybersecurity, Python programmers are high in demand. 

But like all programming languages, in Python, everything depends on you, the programmer. You decide whether your code looks professional or ugly.

Fortunately, Python has a vast array of built-in features that can help you write code that is elegant, concise, and expandable, the kind of characteristics you expect from a professional programmer.

Here some of my favorite tricks, which I think every Python developer should know.

1. List comprehensions

List comprehensions are one of the key features of Python that help you write code that is more concise and elegant. Let’s say you want to create an array containing numbers from 1 to 100. The convenient way to do it would be to use the range() function:

numbers = list(range(1, 101))

But what if you wanted to do something more complicated, such as creating a list of squares from 1 to 100? In that case, the classic way would be to use a for loop:

numbers = []

for i in range(1, 101):

    numbers.append(i**2)

This is the standard way that most programming languages support. But fortunately, in Python, list comprehensions make things a lot easier. Here’s what the same code would look like when written in list comprehension mode:

numbers = [i**2 for i in range(1, 101)]

You can use list comprehensions to pack several instructions and expressions in the brackets that define a list. They’re much shorter and more elegant than the for loop. And there’s a lot more you can do while still keeping your code clean. For instance, say you have a function is_prime() that checks an input number and returns True if it’s a prime number. The following code snippet creates a list of squares of primes from 1 to 100 by adding is_prime() a condition to the comprehension.

2. Zipping

Another Python feature that comes handy every once in a while is the zip() function. zip combines two or more lists into a single variable. Say you have collected a list of customer names, their ages, and their favorite ice cream flavor.

customers = ['John', 'Natasha', 'Eric', 'Sally']

ages = [26, 31, 39, 22]

flavors = ['cherry', 'chocolate', 'strawberry', 'lemon'] 

Using zip(), you can consolidate all three lists into a single list where each entry contains a tuple with the name, age, and preferred flavor of one customer.

combined = zip(customers, ages, flavors)

customers_ice_cream = list(combined)

This is what your customers_ice_cream list looks like after zipping the data:

[('John', 26, 'cherry'),

 ('Natasha', 31, 'chocolate'),

 ('Eric', 39, 'strawberry'),

 ('Sally', 22, 'lemon')]

And here’s how the zipped list can be used in a loop:

for cust in customers_ice_cream:

        print("{} is {} years old and likes {}".format(*cust))

The output looks like this:

John is 26 years old and likes cherry

Natasha is 31 years old and likes chocolate

Eric is 39 years old and likes strawberry

Sally is 22 years old and likes lemon

3. Counting items

Often, you want to know how many times a certain value occurs in a list. For instance, say we’ve collected a list of 1-10 ratings from an online survey. To simulate this, we’ll generate a list of 1,000 random numbers from 1 to 10 using the randint() function.

from random import randint

ratings = [randint(1, 10) for _ in range(1,1001)]

Now, we want to know how many of each rating is included in the list. One way to do this is to use the built-in count function of the list. count() takes a value and returns the number of times that value occurs in the list.

for i in range(1, 11):

    print("{}: {} occurences".format(i, ratings.count(i)))

This code produces the following output:

1: 95 occurrences

2: 115 occurrences

3: 111 occurrences

4: 109 occurrences

5: 81 occurrences

6: 110 occurrences

7: 80 occurrences

8: 94 occurrences

9: 98 occurrences

10: 107 occurrences

However, this only works if you know in advance what is the range of values in your list. In case you don’t know the possible values, you can use a set, which creates a list of unique items contained in another list. For instance, if you have a list of names and want to know how many times each name has occurred you can use the following code.

for name in set(names):

    print("{}: {} occurences".format(name, names.count(name)))

Alternatively, you can use the Counter class, which specializes in counting values in lists.

from collections import Counter

ratings_count = Counter(ratings)

for rating in ratings_count:

    print("{}: {} occurences".format(rating, ratings_count[rating]))

Counter provides some added functionality, such as the most_common() function, which gives you the most occurring values in a list. For instance, the following code will print out the three most-popular values:

for rating in ratings_count.most_common(3):

    print("{}: {} occurences".format(*rating))

Want to work at Rijksoverheid? They’re looking for Python developers.

4. Enumerating

Sometimes, you want to keep track of the count of items as you iterate through a list. Say you have a list of customer names and you want to list them along with their index number. Here’s one way to do it:

for i in range(len(customers)):

    print("{}: {}".format(i+1, customers[i]))

This would produce an output that looks something like this:

1: Samantha

2: Mara

3: Eric

4: James

5: George

6: Toni

7: Margaret

8: Steven

While this code works, it’s not very elegant. Notice the mismatch between the index and the counter? Fortunately, Python has an enumerate() function that makes your index-tracking code much more understandable and pleasing to the eye. enumerate() takes two arguments, the list you want to enumerate and the starting number of the counter, and gives two outputs at each round of the loop, the counter value and the list item. Here’s what the same problem is solved with enumerate.

for i, customer in enumerate(customers, 1):

    print("{}: {}".format(i, customer)) 

Much better.

5. Parameter expansion

Say you have a function that processes student information:

def process_student_info(first_name, last_name, fav_topic, score):

    print(first_name, last_name, fav_topic, score)

In many cases, the values you want to pass to the function are included in a list or dictionary object that has been populated from a database or text file. In such cases, calling the function would be a bit clunky:

process_student_info(student[0], student[1], student[2], student[3])

Fortunately, Python “parameter expansion” feature, which enables you to directly pass an entire list to a function. By adding a * at the beginning of the list’s name, you expand it to its individual values before submitting it to the function.

process_student_info(*student)

Parameter expansion with lists works as long as the number of parameters and their sequence is similar to the parameters of the target function. In case of a mismatch, it will raise an error.

You can also use parameter expansion with dictionaries, in which case, the order of values doesn’t matter. You only need to have keys that correspond to your function parameters. Parameter expansion for dictionaries requires the ** operator before the object.

student = {'last_name': 'doe', 'score': 89, 'first_name': 'john', 'fav_topic': 'calculus'}

process_student_info(**student)

One of the benefits of using dictionary expansion is that if your function has default parameters, omitting them in your dictionary won’t raise an error.

6. Type annotations

Python is a dynamically-typed language, which means if you try to mix variables that have different data types, it usually finds a way to resolve the difference or raises an exception if it can’t. But this flexibility can also result in unpredictable behavior.

Say you have a function that multiplies two variables.

def mul(a, b):

    return a * b

if you call mul() on two integers or floats — your intended use for the function — the result is predictable.

a = 5

b = 6

print(mul(a, b))

Output: 30

But what if one of your variables is a list?

a = [1, 2, 4, 5]

b = 3

print(mul(a, b))

Output: [1, 2, 4, 5, 1, 2, 4, 5, 1, 2, 4, 5]

The function creates a list that is three concatenated copies of the list. This is clearly not what you wanted to do. Python versions 3.6 and higher provide “type annotations,” a feature that enables you to define the type of data each function argument should take. Here’s how the type annotated mul() function looks like.

def mul(a: int, b: int): 

    return a * b

This format explicitly states that mul() takes two integer values. To be clear, type annotation won’t prevent you from using the function in unintended ways. But there are some good reasons to use it anyway.

First, defining the type of data is a form of documentation, and makes it easier for other developers going through your code to understand what type of data you expect in your function (compare the annotated and non-annotated versions of the function).

But even more importantly, some code editors process type annotations and help you with features such as autocomplete and type errors.

Screenshot of Spyder IDE showing autocomplete for typed variable.

Closing thoughts

These were six of my favorite Python tricks. Using them will make your code concise and elegant. With Python fast becoming the defacto programming language in many domains and institutions, having a good set of best practices will ensure you can be a productive member of your development team.

This article is brought to you by Rijksoverheid. 

Published September 23, 2020 — 13:18 UTC