> programming > python

How to write more beautiful , faster and idiomatic python code

· 7 min read April 14, 2022

banner

“Python tricks” is a tough one, cuz the language is so clean. E.g., C makes an art of confusing pointers with arrays and strings, which leads to lotsa neat pointer tricks; APL mistakes everything for an array, leading to neat one-liners; and Perl confuses everything period, making each line a joyous adventure ;-)

  • Tim Peters

Python is praised for its readability. This makes it a good first language, and a popular choice for scripting and prototyping.

In this post, we will look at some techniques that will make your Python code more readable, fast and idiomatic. I am no mere an expert in python , these tips have helped me a lot in writing good python and I hope these will help you also in the same . If you have any more ideas , we would love to discuss them in comment section below ..

Tip 1. When there is recurring setup and tear down you should use a Context Manager so that you can use with statement

Whenever working with external resources like files and database which involves setup of resource , checking for any exceptions and then finally closing the resource (tear down ) , rather than using the naive the naive approach of use try , except and finally pattern use Context Managers .

Most times when your are working databases , locks and files the standard library provides the context managers which can be used through the with statement . Lets take the trivial example of reading from a file


# non pythonic way

try :
  file  = open("text.txt")
  lines = files.readlines()
except Exception as e:
  print(e)
finally :
  file.close()

# pythonic way much concise, and less bug prone

with open('text.txt') as f :
    f.readlines()

As you can see we no longer need to setup the file and close it . This way is not only short but also conveys the business logic properly. We don’t have to do the setup and tear down ourselves which makes the code less error prone

The Session class of the request module implements the enter and exit methods and can be used as a context manager when you need to preserve cookies between requests, want to make multiple requests to the same host or just want to keep TCP connection alive.


import requests
  with requests.Session() as sess:
     sess.request(method=method, url=url)

Tip 2. Stop adding strings manually use str.join instead .

String objects are immutable in python and any concatenation operation creates a new string, copies the old string character by character and then appends the new string to be concatenated.

Lets suppose we have a list of names that we need to concatenate into a single string separated by commas.

The naive approach for doing this is :


names = ["aabid","john","doe","mathew"]
new_string=""

for name in names :

   new_string.append(name+",")

While this approach seems to be pretty straightforward . But as I told you above that strings being immutable in python the above algorithm has a quadratic runtime which will create problems if the length of names becomes large.

But don’t worry there is a better , easier , and more pythonic way to achieve this with a linear runtime and that is the str.join() .

str.join() takes an iterable as an argument and returns a string which is a concatenation of all the string objects in the iterable.


names = ["aabid","john","doe","mathew"]

new_string = ",".join(names)

Tip 3. Take advantage of magic methods ( dunder methods ) to your best

If you have used Classes in python . You would have also used the init() method to initialize the class state which is a dunder or magic method . magic methods have a double underscore as prefix as suffix thats why they are also called dunder methods

Python uses the word “magic methods”, because those methods really performs magic for you program. One of the biggest advantages of using Python’s magic methods is that they provide a simple way to make objects behave like built-in types. That means you can avoid ugly, counter-intuitive, and nonstandard ways of performing basic operators.

Lets suppose we have a class Stack ( which implements the Stack LIFO datastructure ) and we want ours know the number of items in the stack or length . If you are coming from java or any other language . I might think of adding getLength() method to achieve the same .


class Stack :

    # basic  implementation
    ...

    def getLength(self) :
        pass

The implementation is quite simple but our users need to remember the getLength function .

There is a more pythonic way of getting the length of sequences and you know its len() . To implement the len() method for our class we will use the dunder len() method . remember that dunder methods should not be called directly from code but the interpreter calls them underneath . in this the builtin len() .


class Stack :

    # basic implementation

    ...

    def __len__(self) :

        return len(self.items)

foo = Stack()

# now we can use len to check the no of items in Stack foo

length = len(foo)

Tip 4. Don’t Create getters and setters use @property decorator

Suppose you have a class Book which has fields author and name. You want the field to be read only so that your users will not change the author of a book .

One way to do this by storing the author in a private variable and defining a method getAuthor() to retrieve it .


# non pythonic

class Book :
    def __init__(self,name,author):
        self.name = name
        self.__author = author

    def getAuthor(self) :
        return self.__author


book1 = Book("Clean Code" , "Uncle Bob" )

book.getAuthor()

# >> Uncle Bob

While the above works completely fine . But there is a better way , using the @property decorator.

Python provides us with a built-in @property decorator which makes usage of getter and setters much easier in Object-Oriented Programming.

By default methods decorated with @property are read only can be accessed with using the object.property notation


# pythonic way using @property decorators

class Book :
    def __init__(self,name,author):
        self.name = name
        self.__author = author

    @property
    def author(self) :
        return self.__author


book1 = Book("Clean Code" , "Uncle Bob" )

print(book.author)

# >> Uncle Bob

This doesn’t end here , what if the requirements change in future and your are allowed to mutate the author only if its the name starts with a “S” ( sounds silly but makes a good example )

The non pythonic approach would be to create a setAuthor() method and validate the input in it. But there is a better approach in python and thats the property.setter decorator . which allows you to mutate a property .


class Book :
    def __init__(self,name,author):
        self.name = name
        self.__author = author

    @property
    def author(self) :
        return self.__author

    @author.setter
    def author(self,value):
        # business logic
        ....

book1 = Book("Clean Code" , "Sebastian" )

book.author

# >> Sebastian

book.author = Seb

Tip 5. Use repr for beautiful debugging experience

According to the official documentation, repr is used to compute the “official” string representation of an object and is typically used for debugging.

Lets create a person class and define a repr method for it which will return a string representation for the object .Ideally, the representation should be information-rich and could be used to recreate an object with the same value.


class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __repr__(self):
        rep = f'Person( "{self.name}" , {self.age}  )'
        return rep


# Let's make a Person object and print the results of repr()

person = Person("John", 20)
print(person)

#>>  Person("John", 20 )

Tip 6. Use Custom Exceptions properly

Creating custom exception in python is pretty easy . But that doesn’t mean you should be creating them everywhere.

Only Create a custom exception when you have a complex error . Like suppose you have a cart object which a checkout method. You don’t want the checkout to work if the cart is empty so , creating a Exception CartEmpty makes a sense here .

Don’t suffix the exception name with Exception . I have seen a lot of people ( including me ) naming their exceptions like CartEmptyException . This doesn’t make any sense . Keep it simple CartEmpty .

You don’t need a custom exception every time . There are about 137 exceptions in python . Take advantage of these .

Tip 7. Don’t fear using built ins

Python is a powerful programming language with many built-in features that allow you to do more with less code. For example, the range() function allows you to create a list of numbers easily, and the len() function returns the length of a string or list.

In addition, Python has a number of libraries that you can use to simplify tasks such as data analysis and machine learning. The SciPy library, for example, provides modules for linear algebra, optimization, integration, and more.

Python also has a rich collection of complex datastructures like heap , priory queues , dequeue , queue , orderedDict , and many more . Rather than reinventing the wheel try to take advantage of these . These are way more optimized and fast than you would create yourself

Tip 8. Use Comprehensions in place of loops .

List comprehensions are a Pythonic way to create lists. They are more concise and readable than for loops, which is why they should be used in place of native for loops.

The syntax for list comprehensions is:

[ expression for item in sequence if condition ]

Here is an example of a list comprehension that creates the list of even numbers between 0 and 10:


[ x for x in range(10) if x%2==0]

Tip 9. Use multiple variable assignment any unpacking

Pythons multiple assignment feature is a nice perk that most programming languages lack. In its simplest form, it looks like this:

a = b = "something"

This is nice because it shortens and simplifies code. However, I rarely get to use it. A more practical option is unpacking iterables into multiple variables:

some_list = ["value1", "value2"]
first, second = some_list

This is a better option than assigning values to each variable using indices

Another important use-case of multiple variable assignment is swapping variables without using a temporary variable .

# swapping a variable using a temporary  variable

x= 10
y =20

temp = x
x= y
y=temp

print(x)
# >> 20

print(y)
# >>10

# swapping variables using multiple assignment

x,y = 10,20

x,y = y,x

This solution is way more concise and easy to understand

Thanks for reading till end . I would love to hear about the tips and hacks you use to improve your code .

Some Thing to say ?