Tips for Writing Fast and Idiomatic Python
Tags: python, programming, tips
Published: 2022-04-23 06:30:22


**Orignally posted at [https://www.aabidsofi.com/posts/tips-for-writing-fast-and-idiomatic-python/](https://www.aabidsofi.com/posts/tips-for-writing-fast-and-idiomatic-python/)**

> "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 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


```python

# non pythonic way

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

```python3

# 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.

```python

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 :

```python

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.

```python

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 .

```python

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() .

```python

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 .

```python

# 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

```python

# 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 .

```python

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.


```python

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:

```python

  [ 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:



```python

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:

```python
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 .

```python
# swapping a variable using a temporary  variable

x= 10
y =20

temp = x
x= y
y=temp

print(x)
# >> 20

print(y)
# >>10

```

```python

# 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 .