All Articles

Writing Idiomatic Python Code

1440 anders norrback bornholm nhSSbwpKuJ0 unsplash

You need to understand Python well before you can write idiomatic, or pythonic code in it.

But what does that even mean?

Here are some examples

Falsy and Truthy

Almost all data types can be interpreted as bool-ish. An empty list? Fals-y. A 3-character string? Tru-thy

# Instead of writing something like this
a = [1, 2, 3]
if len(a) > 0:
    ...

# You could write:
a = [1, 2, 3]
if a:
    ...

Ternary operator

Python does have a ternary operator by leveraging one-line πš’πšs:

# Instead of the lenghty version
a = True
value = 0
if a:
  value = 1

# You could shorten it to:
a = True
value = 1 if a else 0

Chained Comparison Operators

Python syntax should be as simple as possible. That’s why you can use mathematics-like notations like this

𝟻 < 𝚑 < 𝟷𝟢

# Instead of this
if x < 10 and x > 5:
  ...

# you can write this
if 5 < x < 10:
  ...

Multiple assignment and destructuring assignment

You can assign different variables in one line of Python code

# Instead of writing
x = 'foo'
y = 'foo'
z = 'foo'
# or
a = [1, 2, 3]
x = a[0]
y = a[1]
z = a[2]

# you could simplify to:
x = y = z = 'foo'
# and
a = [1, 2, 3]
x, y, z = a

f-strings

f-strings provide a template-like mini-language inside Python. You can, for example, align text, or specify precisions of floats.

username = "Bas"
monthly_price = 9.99

# Instead of transforming each element,
print(username.rjust(10), '|', "{:3.2f}".format(monthly_price))

# you can use a single f-string
print(f"{username : >10} | {monthly_price:3.2f}")

list comprehensions / dict comprehensions

list and dict comprehensions are maybe the most Pythonic feature. It can be very useful for modifying data structures.

usernames = ["alice", "bas", "carol"]

# Instead of a loop:
users_with_a = []
for username in usernames:
    if username.startswith("a"):
        users_with_a.append(username)
# Use a list comprehension
users_with_a = [username for username in usernames if username.startswith("a")]

# Same for dicts: Instead of a loop
users_dict = {}
for username in usernames:
    users_dict[username] = get_user_id(username)
# you can use dict comprehensions
users_dict = {username: get_user_id(username) for username in usernames}

in keyword

Python has the in operator that works on collections, like lists.

You could use it to check if an element is in a list of choices

name = 'Alice'
found = False
if name == 'Alice' or name == 'Bas' or name == 'Carol':
  ...

name = 'Alice'
if city in {'Alice', 'Bas', 'Carol'}:
    ...

enumerate

Whenever you need to not only access each element by a list but also need a counter in your loop, you can use enumerate

a = ["A", "B", "C"]

# Instead of using an index variable
for i in range(len(a)):
    print(i, a[i])

# you could iterate the list as usual and attach a counter
for counter, letter in enumerate(a):
    print(counter, letter)

The Walrus Operator

With the walrus operator introduced in Python 3.8, you have an assignment expression. That means that you could assign a value to a variable and access that value in the same line.

n = len(a)
if n > 10:
    print(f"List is too long ({n} elements, expected <= 10)")

if (n := len(a)) > 10:
    print(f"List is too long ({n} elements, expected <= 10)")

assert

Assertions inside your code not only make it safer but also help with understanding your rationale behind a particular line.

# Instead of a comment
def activate_user(user):
    # User has to be a `UserModel` object
    user.active = True
    user.save()

# you can use assert
def activate_user(user):
    assert type(user, UserModel)
    user.active = True
    user.save()

Pattern Matching

Pattern Matching is a very handy feature added in Python 3.10.

I had a Twitter thread on this in March.