Python is one of the most popular languages in the world. It has many practical applications including web development, desktop apps, IoT, data analysis, and machine learning. This lesson contains ten tips that will make your code more pythonic.

To make life simpler, we’ll use the following key to denote a few categories

  • πŸ’© – code that you should avoid.
  • πŸ€” – code that is generally ok, but ignores built-in language features.
  • 🐍 – code that is considered pythonic.
  • πŸ’‘ – bonus tips.

Null checks

A common scenario is checking that a variable isn’t empty (or null) before use. In Python, null is denoted by the keyword None. The two code snippets below produce identical results. However, Python supports a simplified null check using the if keyword followed by the name of a variable.

πŸ’‘ Bonus Tip: Use f-strings for string formatting. Initiate an f-string by typing an f immediately before a regular string (f"…"), and place variables inside curly braces. Python will format the result into a string containing the variables.

# Null check: OK version πŸ€” - Explicit "if x is not None" ❌
n = 42
if n is not None:
    print(f"n exists and is equal to {n}")

# Pythonic version 🐍: Use simplified if βœ…
if n:
    print(f"n exists and is equal to {n}")

Included values

In this section, we need to check if a list (L) contains the value of a particular variable (x). One way to do this is by using a for loop to iterate over all elements and check for equality. Python provides a nice shortcut, using the in keyword.

# Check if a value is contained in a list
L = ["JavaScript", "Python", "Ruby", "PHP", "Rust"]
x = "Rust"

# OK version πŸ€” - For loop and a equality check ❌
for i in range(len(L)):
    if x == L[i]:
        print(f"{x} is contained in the list")

# Pythonic version 🐍: Use "if x in L" βœ…
if x in L:
    print(f"{x} is contained in the list")

List Comprehensions

A common data processing pattern is to define an empty list and append values to it. For example, let’s assume we want to generate a list of square numbers in a certain range. One way to do this is to define a list, and use a for loop to iterate over a range of values and append each value to the list.

Python provides a neat one-liner for this purpose, called a β€œlist comprehension”. To write a list comprehension, start with the expression you would normally pass to the append method. From there, write the for loop condition immediately after the initial expression. Lastly, put everything inside a pair of square brackets. Comprehensions can be used with dictionaries, sets, and generators, however, try to avoid them with complex expressions. Readability is key.

# OK version πŸ€” - For loop and append ❌ 
squares = []
for num in range(12):
    squares.append(num ** 2)

# Pythonic version 🐍: Use a list comprehension βœ…
squares = [num ** 2 for num in range(12)]

# Bonus Tip πŸ’‘: You can also use dictionary, set, and generator comprehensions
squares_dict = {num: num ** 2 for num in range(12)} # dictionary
squares_set = {num ** 2 for num in range(12)}       # set
squares_gen = (num ** 2 for num in range(12))       # generator

Using Any/All

Speaking of one-liners, Python provides some built-in functions that can check conditions that apply to at least one element, or all elements in an iterable. To illustrate this, let’s consider a simple example. Imagine we’re interested to know if a list contains negative numbers. A naive way to check this is to use a combination of a for loop and a flag. A better way is to use the any function.

# Checking for negative values in a list
nums = [1, 2, 3, 4, 5, -42, 6, 7, 8]

# Inefficinet way πŸ€” - Using a for loop and a flag ❌
contains_neg = False # flag
for num in nums:
    if num < 0:
        contains_neg = True

# Pythonic way 🐍 - Using the built-in "any" function βœ…
contains_neg = any(num < 0 for num in nums) # True

# Bonus Tip πŸ’‘: Python also has a built-in "all" function βœ…
contains_neg = not all(num >= 0 for num in nums) # True
  • any – Returns True if a condition applies to any element of the iterable. If the iterable is empty, returns False.
  • all – Returns True if a condition applies to all elements of the iterable (or if the iterable is empty).


Python provides a nice syntax for iteration that many users tend to ignore. For example, when iterating over the values in a list, a common pattern is to use a for loop and an index. If we’re only interested in the values, a neater way is to use a for loop and iterate directly over the list elements. If we need to keep an index, a neater way is to use enumerate.

# Iterating over a single list
L = ["a", "b", "c", "d"]

# OK version πŸ€” - Index in range ❌ 
for i in range(len(L)):
    val = L[i]
    print(i, val)

# Pythonic version 🐍: Access elements directly βœ…
for el in L:

# Pythonic version 🐍: Use enumerate if you need the index, value pair βœ…
for i, val in enumerate(L):
    print(i, val)

πŸ’‘ Bonus Tip: These ideas also apply when iterating over multiple lists. We can iterate directly over values in two collections using zip. If an index is required, we can use a combination of enumerate and zip.

# Bonus Tip πŸ’‘:  Iterating over multiple lists
A = ["a", "b", "c", "d"]
B = ["e", "f", "g", "h"]

# OK version πŸ€” - Index in range ❌ 
for i in range(len(A)):
    va, vb = A[i], B[i]
    print(i, va, vb)

# Pythonic version 🐍: Use zip to get the values βœ…
for va, vb in zip(A, B):
    print(va, vb)

# Pythonic version 🐍: Use a combination of zip and enumerate to get the index and the values βœ…
for i, (va, vb) in enumerate(zip(A, B)):
    print(i, va, vb)

Tuple Unpacking

Another useful thing to know is how to unpack values directly from a tuple. One way is to access each element using indices. A more efficient way is to unpack the elements directly.

# Tuple unpacking
some_tuple = (1, 2, 3)

# OK version πŸ€” - Unpack elements by index ❌
x = some_tuple[0]
y = some_tuple[1]
z = some_tuple[2]

# Pythonic way 🐍 - Unpack elements directly βœ…
x, y, z = some_tuple

Ternary Operators

Python uses if/elif/else blocks for control flow. For example, consider the need to decide on the sign of a variable based on its value. The naive way is to use an if/else block to make the decision. A neater way to simplify this is by using a ternary operator.

# Assign a value based on a condition
a = 42

# OK version πŸ€” - if/else blocks ❌ 
if a > 0:
    sign = "positive"
    sign = "negative"

# Pythonic way 🐍 - Use a ternary operator βœ…
sign = "positive" if (a > 0) else "negative" # parentheses are optional


Generators are a powerful tool to save memory and improve performance. In general, they yield one value at a time and can be iterated over multiple times. Let’s imagine we’re interested in the sum of the first 42 000 natural numbers. We could use a list comprehension to compute the values and call the built-in sum function. Building a list requires 351064 bytes. Using a generator reduces this value to 112 bytes. That’s pretty awesome πŸ”₯.

from sys import getsizeof 

# Inefficent way πŸ’©: Using a list ❌
L = [n for n in range(42_000)]
sum(L) # 881979000 bytes
getsizeof(L) # 351064 bytes

# Efficient way πŸ”₯: Use a generator βœ…
G = (n for n in range(42_000))
sum(G) # 881979000 bytes
getsizeof(G) # 112 bytes

Mutable Default Arguments

Python supports default values for function parameters. If a value for a parameter isn’t passed during a function call, the specified default value is used. There is a danger associated with this if the default value is of a mutable type. For example, consider specifying an empty list as a default value. If the list is modified, the default value is modified as well. In most cases, this is not intended. To avoid it, we can set the default value to None. If no value is passed during the function call, we can ensure an empty list is created.

# Mutable default arguments πŸ’©:  Wrong way  ❌
def append_element(elem, L=[]):
    return L

L1 = append_element(21) # [21]
L2 = append_element(42) # [21, 42] - Oops..

# Correct way πŸ”₯: Use None βœ…
def better_append(elem, L=None):
    if L is None:
        L = []
    return L

L1 = better_append(21) # [21]
L2 = better_append(42) # [42]

Context Managers

The last tip is to use a context manager to ensure that a resource is properly closed. Let’s consider a simple example of writing to a text file. In this case, the simple code will run just fine, however, if more complex logic is involved and an exception is raised during the write, the file won’t be closed. Another common scenario is simply to forget to close the file. Using a context manager ensures the file will always be closed, regardless of any exception.

# Managing files - using open and f.close() ❌
f = open("file.txt", "w")
f.write("Hi mom!") 

# Pythonic way 🐍 -  Use a context manager βœ…
with open("file.txt", "w") as f:
    f.write("Hi mom!")