Sign in
Log inSign up

Is pythonic way the better way?

Nikhil Taneja's photo
Nikhil Taneja
·Jan 11, 2021·

8 min read

Here I have shared some of my thoughts on some of the pythonic way coding style. These do not design patterns (which are best practices used by experienced object-oriented software developers) of any sort, just some ways of writing code differently or I should say more pythonic.

If you might not already know - Python is an interpreted, interactive, object-oriented programming language. But more importantly, Python is a programming language that lets you work more quickly and integrate your systems more effectively.

If you want to learn all the recommended python style conventions/guidelines, you can learn here. So with that, let's start with something which you might already know.

Swapping of values

This is a cool way of swapping values among two variables. Python does not create any temporary variables. It is all done on the stack.

a, b = b, a

This tuple unpacking not only makes code more readable but also handles the state better.

List comprehension

Most people working with python at a certain point of time discover about list comprehensions which are shorthand syntax of writing for a loop.

a = [i**2 for i in range(10)]

Awesome right, short and simple. If you use round brackets () instead of square brackets [] instead of returning a completely new list, it returns a generator object. Generators in python are super cool, they yields one value at a time which requires less memory. Any python function with a keyword "yield" may be called a generator. When the generator encounters a yield keyword the state of the function is frozen and all the variables are stored in memory until the generator is called again.

>>> a = (i**2 for i in range(10))
>>> print(a)
<generator object <genexpr> at 0x0000025CADCFA518>
>>> print(sum(a))
285

This is where the shortcomings of pythonic ways are prevalent amongst beginners, most beginners often use square brackets even when not required.

Compound comparison statements

A compound statement consists of one or more 'clauses'. An example for this will be

>>> x = 5
>>> 10 < x < 20
False
>>> x = 15
>>> 10 < x < 20
True
>>> x = 25
>>> 10 < x < 20
False

A compound comparison is a neat feature in python. In other languages, you would need to express this as two different comparisons joined with an and operation

>>> x = 15
>>> (10 < x) and (x < 20)
True

Distinguishing multiple exit points in loops

This is sort of like a replacement for flag variables. There are two ways you can exit out of the for loop 1) You hit the break; or 2) You did not. In the second case, else block will get executed.

>>> def find(seq, target):
...     for i, value in enumerate(seq):
...         if value == target:
...             break
...     else:
...         return -1
...     return i
... 
>>> find([3,4,5], 5)
1
>>> find([3,4,5], 6)
-1

I do not think there is any reason not to use this except the poor naming of else. I have not seen this used in many codebases maybe because it has a very niche use case, unlike list comprehension.

Ternary operator

Blueprint of general syntax

(if_test_is_false, if_test_is_true)[test]

Example usage

a = ('Odd', 'Even')[num//2 == 0]

This sounds a lot useful and you wonder why you might not have heard of this before or seen something like this in some codebase. There is a reason behind this, to understand consider this example

>>> (print('isFalse'), print('isTrue'))[True]
isFalse
isTrue

As you can see it prints both cases. Because of this not only extra processing is wasted but it can introduce some bugs. Ternary if else cannot be used to handle None value (or null value for non-python world).

>>> import re
>>> message = 'Is pythonic way the better way?'
>>> match = re.search(r'python', message)
>>> (match.group(), 'Not found')[match == None]
'python'
>>> match = re.search(r'python3', message)
>>> (match.group(), 'Not found')[match == None]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'group'

We received this error because when re.search fails, it returns None and we called a method on the something which was None. This happens because with the tupled ternary technique, the tuple is first built, then an index is found as a result both True case along with False gets executed. So use this with a lot of care because the way I see this, it not a replacemrnt for if-else!

Walrus operator available from Python 3.8 (PEP 572)

Walrus operator can be used to consolidate an assignment statement and a boolean expression when both assignment and expression would utilize a similar statement.

>>> import re
>>> message = 'Is pythonic way the better way?'
>>> if(match := re.search(r'python', message)):
...     print(match.group())
... 
python
>>> if(match := re.search(r'python3', message)):
...     print(match.group()) # No output
...

Parentheses are important as without that it would assign result of boolean expression instead of the statement.

>>> my_list = [1,2,3,4,5]
>>> if (n := len(my_list)) > 3:
...     print(n)
...
5
>>> if n := len(my_list) > 3: # without parenthesis
... print(n)
...
True

Some good reads

  • This article has been inspired by a talk from PyCon US 2013 by Raymond Hettinger go watch it here
  • Mind-bending edge cases in python that make you say "Wat‽" go watch it here
  • There is a fun project attempting to explain what exactly is happening under the hood for some counter-intuitive snippets and lesser-known features in Python which you can read here

Thank you!

I hope you find this article insightful, please share it with your friends, you can read my other articles at my blog

If I have missed something, please write a comment and I will update my article.