In [1]:
name = '2017-10-30-pythonic-code'
title = 'Writing Pythonic code'
tags = 'basics'
author = 'Denis Sergeev'

In [2]:
from nb_tools import connect_notebook_to_post
from IPython.core.display import HTML

html = connect_notebook_to_post(name, title, tags, author)

Writing idiomatic python code

the Zen of Python


In [3]:
import this


The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

PEP8

Naming conventions

  • Names should be descriptive!
  • Rule of thumb: If you need a comment to describe it, you probably need a better name.
import my_module_name
from foo import bar

# constant, not changed at runtime
MODEL_GRID = 'cartesian'


def calculate_something(data):
    """Compute some parameters"""
    # ...
    return result


class ModelDomain(object):
    """
    Very long description...
    Attributes: ...
    Parameters: ...
    """
    def __init__(self, name='', description='', bounds=None, mask_bounds=None):
        self.name = name
        self.description = description
        # something else...

    def mask_var(self, data):
        """Mask the data of the given variable outside the region."""
        return data.where(self.mask_bounds, data)


class MyDescriptiveError(Exception):
    pass

A handful of foundational concepts

Truthiness


In [4]:
if []:
    print('this is false')

In [5]:
False     # false is false
[]        # empty lists
{}        # empty dictionaries or sets
""        # empty strings
0         # zero integers
0.00000   # zero floats
None      # None (but not zero)

Thuthiness is defined by __bool__() method


In [6]:
class MyClass:
    def __init__(self, data):
        self.data = data
    def __bool__(self):
        if len(self.data) > 0:
            return True
        else:
            return False

In [7]:
foo = MyClass(data=[1, 2, 3])

In [8]:
if foo:
    print('it contains some data')
else:
    print('no data')


it contains some data

What's the pythonic way?


In [9]:
vrbl = True

In [10]:
if vrbl:
    # NO: if condtion == True
    print('do something')


do something

Don't be this guy:


In [11]:
def test_if_greater_than_ten(x):
    return True if x>10 else False

In [12]:
test_if_greater_than_ten(11)


Out[12]:
True

In [13]:
def fun():
    print('blah')

In [14]:
x = fun

In [15]:
x()


blah

In [16]:
type(x)


Out[16]:
function

In [17]:
callable(x)


Out[17]:
True

In [18]:
isinstance(12345, (int, float))


Out[18]:
True

Testing for None

if something is None:
    print('no results')
else:
    print('here are some results')

negation:

if something is not None:
    print('Option A')
else:
    print('Option B')

Multiple tests against a single variable


In [19]:
my_axis = 'x'

In [20]:
if my_axis in ('x', 'y'):
    # Instead of writing like that:
    # if my_axis == 'x' or my_axis == 'y'
    print('horizontal')
elif my_axis == 'z':
    print('vertical')


horizontal

Sometimes can be a bit slower if used inside a long loop.

Checking for type


In [21]:
a = [1,2,3]

How to check if the variable is a list or dictionary?


In [22]:
import numpy as np

In [23]:
a = np.arange(10)

In [24]:
isinstance(a, np.ndarray)


Out[24]:
True

Checking if an object is iterable?


In [25]:
the_variable = [1, 2, 3, 4]
another_variable = "This is my string. There are many like it, but this one is mine."
and_another_variable = 1000000000000

In [26]:
for i in another_variable[:10]:
    # iterate over the first 10 elements and print them
    print(i)


T
h
i
s
 
i
s
 
m
y

In [27]:
import collections

In [28]:
if isinstance(1234563645, collections.Iterable):
    # iterable
    print('It is iterable')
else:
    # not iterable
    print('It is NOT iterable')


It is NOT iterable

Similar way, by checking the methods:


In [29]:
hasattr(the_variable, '__iter__')


Out[29]:
True

Another way: duck-typing style

Pythonic programming style that determines an object's type by inspection of its method or attribute signature rather than by explicit relationship to some type object ("If it looks like a duck and quacks like a duck, it must be a duck.") By emphasizing interfaces rather than specific types, well-designed code improves its flexibility by allowing polymorphic substitution. Duck-typing avoids tests using type() or isinstance(). Instead, it typically employs the EAFP (Easier to Ask Forgiveness than Permission) style of programming.

try:
   for i in the_variable:
        # ...
except TypeError:
   print(the_variable, 'is not iterable')

Modern string formatting


In [30]:
day = 30
month = 'October'

won't work, because Python does not automatically convert to str:

print('Today is ' + month + ', ' + day)

Works, but not pythonic:


In [31]:
print('Today is ' + month + ', ' + str(day))


Today is October, 30
Pythonic:

In [32]:
print('Today is {}, {}'.format(month, day))


Today is October, 30

In [33]:
print('Today is {1}, {0}'.format(month, day))


Today is 30, October

In [34]:
print('Today is {1}, {0}. Tomorrow will be still {0}'.format(month, day))


Today is 30, October. Tomorrow will be still October

In [35]:
print('Today is {m}, {d}. Tomorrow will be still {m}. And again: {d}'.format(m=month, d=day))


Today is October, 30. Tomorrow will be still October. And again: 30
using dictionaries

In [36]:
data = {'dow': 'Monday', 'location': 'UEA', 'who': 'Python Group'}

In [37]:
print('Today is {dow}. We are at {location}.'.format(**data))


Today is Monday. We are at UEA.
f-strings (Python 3.6+)

Just pulling variables from the namespace!


In [38]:
print(f'Today is {day}th. The month is {month}')


Today is 30th. The month is October

In [39]:
# print(f'Today is {data["dow"]}. We are at {data["location"]}')

References


In [40]:
HTML(html)


Out[40]:

This post was written as an IPython (Jupyter) notebook. You can view or download it using nbviewer.