The world’s leading publication for data science, AI, and ML professionals.

5 Things That Will Instantly Make Your Python Code More Pythonic

Understanding The Rules Of The Language

Photo by Alex Chumak on Unsplash
Photo by Alex Chumak on Unsplash

Anyone that speaks multiple languages understands that the rules of one language don’t necessarily apply to another; I’m a native English speaker, but I also speak Twi which is a dialect of the Akan tribe in Ghana. I force myself to speak Twi whenever I’m in Ghana – I make a lot of mistakes, but the locals are friendly and they help me out.

The mistakes I make aren’t necessarily things that would corrupt the meaning of what I said, it’s usually just bad practice for the Twi language and they’d often make the same type of mistake when speaking English. For example, it’s commonplace for Ghanaians to say please at the beginning of everything they say when they communicate in Twi so they do the same when they are speaking English.

Although "please yes" is not necessarily bad use of English grammar, you probably wouldn’t say it. But this error stems from trying to apply the rules of English to Twi. Many programmers who code in various languages get caught in this trap too.

Your code may not throw an error, but you’re making life harder for other programmers when they try to make sense of what you’ve written. Taking time to understand the best practices of a language is beneficial to not only yourself but to others who have to collaborate with you. For that reason, we’re going to cover five standard approaches to writing Pythonic code.

1 enumerate() instead of range()

One of the most common misuses of Python you’ll see is programmers using the range() and len() functions to loop over a list [or sequence] and generate indexes.

names = ["john", "doe", "jane", "plane"]
for idx in range(len(names)): 
    print(idx, names[idx])
0 john
1 doe
2 jane
3 plane

You’ll find that the code above is valid and will run fine, so why is this a problem? It goes against one of the things that have made Python such a successful Programming language: readability.

Using the range(len(names)) convention is easy to do but is less than ideal because it compromises readability. A better way to perform the same functionality is to pass the list to the built-in enumerate() function, which will return an integer for the index and the element in the sequence.

names = ["john", "doe", "jane", "plane"]
for idx, name in enumerate(names): 
    print(idx, name)
0 john
1 doe
2 jane
3 plane

If you don’t need the indexes then you can still loop through the elements as follows:

names = ["john", "doe", "jane", "plane"]
for name in names: 
    print(name)
john 
doe
jane
plane

2 Use the with statement

You may be writing a program that requires you to read or write a file. The open() and close() built-in functions allow developers to open and close files respectively.

requirements = open("requirements.txt", "w")
requirements.write(
  "scikit-learn >= 0.24.2, < 0.25.0", 
  "numpy >= 1.21.2, < 1.22.0",
  "pandas >= 1.3.3, < 1.4.0"
)
requirements.close() 

In the above code, we opened a text file called requirements.txt, wrote some contents, then closed it when we were finished. This code is completely valid but it’s what Python programmers would call unpythonic.

It’s also quite dangerous since it’s pretty easy to forget to close a file, and sometimes it’s out of our hands – like when an error occurs in a try clause and the program skips the close() call to the except clause.

try: 
    requirements = open("requirements.txt", "w")
    requirements.write(
      "scikit-learn >= 0.24.2, < 0.25.0", 
      "numpy >= 1.21.2, < 1.22.0",
      "pandas >= 1.3.3, < 1.4.0"
    )
    random_error = 25 / 0 # raises a 0 divide exception
    requirements.close() # this is skipped 
except: 
   print("An error occurred.")

The scenario above is pretty unlikely: I can’t imagine an instance in which you’re writing the dependencies and then dividing by some numbers. But the threat of a disaster is very real.

In the code above, the close() method is skipped because an error was found in our code which makes it skip to the except block. This can lead to extremely hard to trace, file corruption bugs later down the line.

A better way to open() and close() files is as follows:

with open("requirements.txt", "w") as requirements:
    requirements.write(
      "scikit-learn >= 0.24.2, < 0.25.0", 
      "numpy >= 1.21.2, < 1.22.0",
      "pandas >= 1.3.3, < 1.4.0"
    )
    requirements.close()

The code above is more Pythonic and much safer since the file is always closed once the execution leaves the with statement block.

3 Compare None values with is

Comparing None values with the is identity operator is preferred to the equality operator ==.

"comparisons to singletons like None should always be done with is or is not, never the equality operators."

  • PEP 8

The reason for this is in how they conduct their comparisons:

  • The equality operator == compares the values of two objects.
  • The is identity operator compares two objects’ identities. Thus, it’s a reference for equality which means it determines if two objects have the same identity.

If this all sounds like jargon, the simple way to put it is that two objects that have the same values are not necessarily identical in Python.

# Example of equal values and identities
a = [1, 2, 3, 4, 5] 
b = a 
id(a) 
"""
140049469156864
"""
id(b)
"""
140049469156864
"""
a is b
"""
True
"""
a == b
"""
True
"""
# Example of equal values but different identities 
a = [1, 2, 3, 4, 5] 
b = a[:] 
id(a) 
"""
139699152256576
"""
id(b)
"""
139699151636672
"""
a is b
"""
False
"""
a == b
"""
True
"""

When you’re comparing a value to None you should always use is because the equality operator == could still evaluate to True even if the object is in fact None.

class Example: 
    def __eq__(self, other=None):
        return True
example = Example()
example == None
"""
True
"""
example is None
"""
False
"""

This possibility occurs as a result of overloading the == operator. Doing example is None literally checks to see whether the value in the example identifier is None. Basically, if a variable is set to None then comparing it to see if it is None will always evaluate to True – since the behavior is predictable, it’s preferred.

Raw strings have a purpose

A string prefixed with a r or R in Python is called a raw string.

print(r"This is a raw string") 
"""
This is a raw string
"""
print(R"This is also a raw string")
"""
This is also a raw string
"""

The most common usage of a raw string is when we’re handling a string that uses several escape characters ` (i.e. windows paths, regular expressions, or if we want to insert text into a string literal that would otherwise be impossible like‘` ).

# without raw strings 
print("This is Kurtis' phone")
"""
This is Kurtis' phone
"""
print("C:Userskurtisdocuments") 
"""
C:Userskurtisdocuments
"""
# with raw strings 
print(r"This is Kurtis' phone")
"""
This is Kurtis' phone
"""
print(r"C:Userskurtisdocuments")"""
C:Userskurtisdocuments
"""

Raw strings shouldn’t be considered as a different type of string data type – it’s not. It’s merely a convenient way to type strings that contain several backslashes.

F-strings are better to format code

One of the new features added in Python 3.6 was f-strings (shorthand for format strings). They offer a more concise and convenient way to format strings – also aligning with Python’s readability object.

To understand its usefulness, we have to see the evolution to this point. Initially the + operator was used to concatenate strings:

name = "John"
age = "30"
city = "London"
print("Hi, my name is " + name + " and I'm " + age + " years old. I live in " + city )

Although this method works, it contains several quotes and + operators which distort the readability.

Python then introduced the conversion specifier %s to make it more specific:

name = "John"
age = "30"
city = "London"
print("Hi, my name is %s and I'm %s years old. I live in %s" % (name, age, city))

This also works but is still not the best for readability.

Here’s how we do the same with f-strings:

name = "John"
age = "30"
city = "London"
print(f"Hi, my name is {name} and I'm {age} years old. I live in {city}")

Much cleaner, and more Pythonic.

Thank you for reading.

Connect with me: LinkedIn Twitter Instagram

If you enjoy reading stories like this one and wish to support my writing, consider becoming a Medium member. With a $5 a month commitment, you unlock unlimited access to stories on Medium. If you use my sign-up link, I’ll receive a small commission.

Already a member? Subscribe to be notified when I publish.

Get an email whenever Kurtis Pykes publishes.


Related Articles