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)]