Python best practices

by leonardo maffi

Version 1.30, Jun 16 2011

Sometimes even good programmers at their first tries of Python use less than optimal solutions and language constructs. In the years Python has accumulated few redundancies and few warts (and some of them will be removed with Python 3.0. This article refers to CPython V.2.5, it's not about Python 2.6 yet, or about Jthon, or PyPy or IronPython, that may have subtle differences), but it's a generally clean language still, so with a page like this you can avoid the most common ones. This page hopes to be really simple and short enough, you can find explanations online elsewhere.

For some things I may be wrong, but this page comes from some experience, so when you don't agree with me, I suggest you to go look for the truth inside many newsgroups and Web pages, just don't assume you are right. If you find I am wrong, or you have questions or comments my email address can be found in my Home Page, I'll glady improve this page and learn from my mistakes.

[Go back to the index (email in the Home Page)]

Bad
Good
x=5
if ( (x==8) and (y>5) ) : ...
1<<5&2
return(5);
while (x<5) : ...
7.
x = 5
if x == 8 and y > 5: ...
(1 << 5) & 2
return 5
while x < 5: ...
7.0
print x,x*x+1
v[i + 1 + a] + v[i + 2 + b]
# Sometimes rules can be broken, for
# example to show grouping better:
print x, x*x + 1
v[i+a+1] + v[i+b+2]
def Function ( x ): ... def function(x): ...
class fooclass: ... class Fooclass(object): ...
d = dict() freqs = {}
# Descriptive names are often better
# But in small scopes a short name may be fine
list = [1, 2, 3]
dict = {'alpha': 0x1234}
sum = x + y
# Don't shadow builtin names
values = [1, 2, 3]
symbol_address = {'alpha': 0x1234}
tot = x + y
"some string" and 'some string' and
"""some string""" and '''some string'''
are the same string.
mapping = { 5 :"5", 6:"6" } mapping = {5: "5", 6: "6"}
mapping = {5 : "5", 6 : "6"}
if mapping.has_key(6): ...
mapping = {5: "5", 6: "6"}
if 6 in mapping: ...
def function( x, l = [] ): ... # Generally don't use mutables as a default
def function(x, items=None): ...
    if items is None:
        items = []
if x == None: ... if x is None: ...
x = 1
if z > 5:
  var1 = 55
# Always use 4 spaces as indent
# (Or always a Tab, but it's less good)
x = 1
if z > 5:
    var1 = 55
mapping = {5 : "5", 6 : "6"}
for key, val in mapping.items(): ...
for key in mapping.keys(): ...
# Use iter* methods when possible
mapping = {5: "5", 6: "6"}
for key, val in mapping.iteritems(): ...
for key in mapping: ...
for i in range(10, 20000): ... for i in xrange(10, 20000): ...
# Use to denote the code that has to
# run when a module is executed and not
# imported:
if __name__ == '__main__':
# Python profiler:
python -m profile -o stats myscript.py
>>> import pstats
>>> p = pstats.Stats('stats')
>>> p.sort_stats('time').print_stats(15)
For source code with not 7-bit ASCII
add this on top:
# -*- coding: UTF-8 -*-
# Or just, if you have less memory:
# coding: latin
al = [1, 2, 3]
for i in xrange(len(al)-1, -1, -1):
    del al[i]
items = [1, 2, 3]
del items[:]
# But often if speed isn't critical you
# can just use (but this is a different
# thing, this creates a new list):
items = []
# If you just want to remove one refence
# to the list:
del items
repeat
    xxx
until yyy
# Equals to:
while True
    xxx
    if yyy: break
# To add a zip file containing modules
# to the search path:
sys.path.append("some.zip")
a = 5
b = 6
aux = a
a = b
b = aux
a = 5
b = 6
a, b = b, a # swap
if x < 10 and x > 2: ... if 2 < x < 10: ...
a = 5
b = 5
c = 5
a = b = c = 5
if x == 1: y = fun1(x)
else if x == 2: y = fun2(x)
else if x == 3: y = fun3(x)
else: y = None
if x == 1: y = fun1(x)
elif x == 2: y = fun2(x)
elif x == 3: y = fun3(x)
else: y = None
# But sometimes a dict is better:
funs = {1: fun1, 2: fun2, 3: fun3}
y = funs.get(x, lambda x:None)(x)
mapping = {5 : "5", 6 : "6"}
for key in mapping.iterkeys(): ...
mapping = {5: "5", 6: "6"}
for key in mapping: ...
al = [1, 2, 3]
for i in xrange(len(al)):
    print al[i]
al = [1, 2, 3]
for el in al:
    print el
al = [1, 2, 3]
for i in xrange(len(al)-1, -1, -1):
    print al[i]
al = [1, 2, 3]
for el in reversed(al):
    print el
class Test(object):
    def __init__(I, x): ...
class Test(object):
    def __init__(self, x): ...
# Compute the sum of the ...
def sum_of(x, y, z): ...
def sum_of(x, y, z): ...
    """Compute the sum of the ..."""
from operator import add
sl = ["ab", "cd", "ef"]
all = ""
for s in sl:
    all += s
# Or:
sl = ["ab", "cd", "ef"]
all = reduce(lambda x,y: x+y, sl, "")
sl = ["ab", "cd", "ef"]
all = "".join(sl)
a = "this isn't a word, right?"
a = a.replace("'", " ")
a = a.replace(".", " ")
a = a.replace("?", " ")
a = a.replace(",", "")
# .replace can be fine. This is faster:
from string import maketrans
tab = maketrans("'.?", "   ")
a = "this isn't a word, right."
afilt = a.translate(tab, ",")
values = ["stop",0,0] values = ["stop", 0, 0]
def mul(x, y): return x*y
l = [2, 3]
print apply(mul, l)
def mul(x, y):
    return x * y
l = [2, 3]
print mul(*l)
vals = [2, 3, -5, 0]
result = []
for el in vals:
    if el > 0:
        result.append(el * el)
vals = [2, 3, -5, 0]
result = [el * el for el in vals if el > 0]
l = [0] * 4
m = [l] * 4
m[1][1] = 5
print m
# One correct way to create a matrix:
m = [[0] * 4 for _ in xrange(4)]
m[1][1] = 5
print m
a = 1
print a / 2, a / float(2)
# A kind of alternative:
from __future__ import division
a = 1
print a // 2, a / 2
class Foo(object):
    def __init__(self, x, y, z):
        self.x_public = x
        self.y_private = y
        self.z_veryprivate = z
    def getx(self):
        return self.x_public
print Foo(1, 2, 3).getx()

# Generally getters and setters are not used.
# Instance names starting with _ are meant as
# 'to not mess with' by convention.
# Instance names starting with __ are private
# and receive name mangling.
class Foo(object):
    def __init__(self, x, y, z):
        self.x_public = x
        self._y_private = y
        self.__z_veryprivate = z
print Foo(1, 2, 3).x_public

finder = re.compile("^\s*([\[\]])\s*([-+]?\d+)
\s*,\s*([-+]?\d+)\s*([\[\]])\s*$")
finder = re.compile(r"""
    ^ \s*             # start at beginning+ opt spaces
    ( [\[\]] )        # Group 1: opening bracket
        \s*           # optional spaces
        ( [-+]? \d+ ) # Group 2: first number
        \s* , \s*     # opt spaces+ comma+ opt spaces
        ( [-+]? \d+ ) # Group 3: second number
        \s*           # opt spaces
    ( [\[\]] )        # Group 4: closing bracket
    \s* $             # opt spaces+ end at the end
    """, flags=re.VERBOSE)
# Sometimes it's positive to indent logically those
# lines just like code.

# Sometimes it can be positive to compose REs:
spaces = r"\s*"            # optional spaces
number = r"( [-+]? \d+ )"  # Group
bracket = r"( [\[\]] )"    # Group. Closing bracket
parts = ["^", bracket, number, ",", number, bracket, "$"]
finder = re.compile(spaces.join(parts), flags=re.VERBOSE)
def function(data):
    """A comment"""
    ...implementation...
# Use doctests (or module tests):
def function(data):
    """A comment

    >>> function()
    None
    >>> function(1)
    result1
    >>> function("a")
    Traceback (most recent call last):
      ...
    TypeError
    """
    ...implementation...

if __name__ == "__main__":
    import doctest
    doctest.testmod()
    print "Tests done."

x = (1, 2, 6, 55, 63, 96, 125, 256, \
     301, 456, 958, 1256, \
     1359, 2568, 3597)
x = (1, 2, 6, 55, 63, 96, 125, 256,
     301, 456, 958, 1256,
     1359, 2568, 3597)
# Too much long lines must be broken with \
# but \ isn't necessary inside () [] {}
from Tkinter import *
from mymodule import *
import Tkinter as tk
from mymodule import fun1, Class1, baseconvert as bc
import psyco
psyco.bind(myfun1)
a = [3.56, 2.12]
try:
    import psyco
    # Psyco classes may be very useful
    from psyco.classes import __metaclass__
    psyco.bind(myfun1)
except ImportError: pass

# Using psyco array.array of double and
# signed long become very fast
import array
a = array.array("d", [3.56, 2.12])
# In some situations arrays of chars too are fast

# psyco can be slow with itertools, map, filter
# and generators, but fast with list
# comprehensions. For max speed with Psyco
# use low level coding style.
  # to print strings without spaces between:
from sys import stdout
stdout.write(string1)
stdout.write(string2)
  This is good enough:
words = ['me', 'do' 'bye', 'taz', 'foo', 'bar']
A shorter, more readable, but slower alternative:
words = 'me do bye taz foo bar'.split()
# sorting on the second item of the tuple
# try to remove the i index from the temporary tuples
lp = [(5J,"b"),(2J,"c"),(3+1J,"a"),(1+2J,"a")]
lp2 = [(c, i, n) for i,(n, c) in enumerate(lp)]
lp2.sort()
print [(n, c) for (c, i, n) in lp2]
from operator import itemgetter
lp = [(5J, "b"), (2J, "c"), (3+1J, "a"), (1+2J, "a")]
print sorted(lp, key=itemgetter(1))
vals = [5, 7 ,8]
tot = -2.0
for v in vals:
    tot += v
vals = [5, 7 ,8]
tot = sum(vals, -2.0)
ll = [[1, 2, 3], [4], [5, 6]]
print sum(ll, [])
data = [[1, 2, 3], [4], [5, 6]]
result = []
for sublist in data:
    result.extend(sublist)

# Or even, for max speed
from itertools import imap
data = [[1, 2, 3], [4], [5, 6]]
result = [None] * sum(imap(len, data))
pos = 0
for sublist in data:
    lensl = len(sublist)
    result[pos : pos+lensl] = sublist
    pos += lensl
print "%s %s" % (string1, string2)
print '"' + chr(c) + '":', freq[c]
print string1, string2
print '"%c": %d' % (c, freq[c])
[' ', c][c.isalpha()] # For Python V.2.5+:
(c if c.isalpha() else ' ')
# How to invert string, lists, etc.
alist[::-1]
astring[::-1]
# To negate (inplace) each second
#  element of alist:
result = []
for (i, v) in enumerate(alist):
    # faster than i % 2
    if i & 1 == 0:
        result.append(v)
    else:
        result.append(-v)
alist[:] = result
from operator import neg
alist[1::2] = map(neg, alist[1::2])

# Or a bit slower but easier to read:
alist[1::2] = [-el for el in alist[1::2]]
# To shallow copy a list or dict:
# (tuples don't need to be copied)
newlist = list(alist)
newdict = dict(adict)
# Or just:
newlist = list[:]
import sys
sys.exit()
# To stop a console program:
raise SystemExit

#Or just:
exit()
if type(s) == type(""): ...
if type(seq) == list or \
   type(seq) == tuple: ...
if isinstance(s, basestring): ...
if isinstance(seq, (list, tuple)): ...
# Or even:
if hasattr(seq, "__getitem__"): ...
# But quite often in dynamic languages you
# don't test types, you just use them (look
# for duck typing), catching exception that
# may occur.
name1 = 5; name2 = 20; print name2
a = 1
b = 2
c = 3
name1 = 5
name2 = 20
print name2
a, b, c = 1, 2, 3
prima = 1
rossa = "Il colore rosso"
léger = 30
# English only for names:
first = 1
red = "Il colore rosso"
light = 30
__del__ method of classes is
usually left undefined.
try:
    fin = file("absent_file.txt")
except:
    ...
try:
    something()
except:
    ...
# Generally specify what exception to catch:
try:
    fin = file("absent_file.txt")
except IOError:
    ...
try:
    something()
except someException:
    ...
except ImportError, IOError: ... except (ImportError, IOError): ...
bytes = array.array('B', [0] * nbytes)
# Or:
from itertools import repeat
bytes = array.array('B', repeat(0, nbytes))
# This can be much faster
bytes = array.array('B', [0]) * nbytes
freqs = {}
for c in "abracadabra":
    try:
        freqs[c] += 1
    except:
        freqs[c] = 1
# Short way:
freqs = {}
for c in "abracadabra":
    freqs[c] = freqs.get(c, 0) + 1

# Often the fastest way:
freqs = {}
for c in "abracadabra":
    if c in freqs:
        freqs[c] += 1
    else:
        freqs[c] = 1

# Or better with Python 2.5+:
from collections import defaultdict
freqs = defaultdict(int)
for c in "abracadabra":
    freqs[c] += 1

someitems = set([1, 2, 3])
somemap = {1:2, 3:4, 5:6}
print list(someitems)[0]
print list(somemap)[0]

someitems = set([1, 2, 3])
somemap = {1: 2, 3: 4, 5: 6}
print iter(someitems).next()
print iter(somemap).next()
from time import clock # This works well on Windows and Linux:
from timeit import default_timer as clock
# Or often use the timeit module

I have to thank many people for suggestions and spotting typos. Thanks to Mark Dufour for fixing few errors, Francesco Brasini for another bug spotted, and Ludvig Ericson for more suggestions/problems found. Another little bug was spotted by pkrumins and Thijs Blaauw. A typo spotted by d0mine. A couple suggestions by attack. Alternative regular expression suggested by ken. A suggestion by greml1n. Two suggestions by arn.zart. Fixed an HTML coversion error found by Olivier Laurent. Typo found by Paolo Orru'. Typo found by Almog Melamed.

See PEP 8 too for more style guides for Python code: http://www.python.org/dev/peps/pep-0008/

[Go back to the index (email in the Home Page)]