4 The importance of readability Most time is spent on maintenance Think about the human reader Can you still read your own code... –next month? –next year?
5 Writing readable code Be consistent (but not too consistent!) Use whitespace judicously Write appropriate comments Write helpful doc strings –not novels Indicate unfinished business
6 Modifying existing code Conform to the existing style –even if its not your favorite style! –local consistency overrides global Update the comments!! –and the doc strings!!!
7 Organizing code clearly Top-down or bottom-up? Pick one style, stick to it Alternative: group by functionality –eg: constructor, destructor housekeeping low level methods high level methods
8 When to use classes (...and when not!) Use a class: –when multiple copies of state needed e.g.: client connections; drawing objects Use a module: –when on copy of state always suffices e.g.: logger; cache Use functions: –when no state needed; e.g. sin()
9 Class hierarchies Avoid deep class hierarchies –inefficient multi-level lookup –hard to read find method definitions –easy to make mistakes name clashes between attribute
10 Modules and packages Modules collect classes, functions Packages collect modules For group of related modules: –consider using a package minimizes chance of namespace clashes
11 Naming conventions (my preferred style) Modules, packages: lowercase except when 1 module ~ 1 class Classes: CapitalizedWords also for exceptions Methods, attrs: lowercase_words Local variables: i, j, sum, x0, etc. Globals: long_descriptive_names
12 The main program In script or program: def main():... if __name__ == __main__: main() In module: def _test():... if __name__ == __main__: _test() Always define a function!
16 When NOT to use comments Dont comment whats obvious n = n+1 # increment n Dont put a comment on every line Dont draw boxes, lines, etc. #------------------------ def remove_bias(self): #------------------------ self.bias = 0
17 One more thing... UPDATE THE COMMENTS WHEN UPDATING THE CODE! (dammit!)
28 When to catch exceptions When there's an alternative option try: f = open(".startup") except IOError: f = None # No startup file; use defaults To exit with nice error message try: f = open("data") except IOError, msg: print "I/O Error:", msg; sys.exit(1)
29 When NOT to catch them When the cause is likely a bug need the traceback to find the cause! When the caller can catch it keep exception handling in outer layers When you don't know what to do try: receive_message() except: print "An error occurred!"
31 Error reporting/logging Decide where errors should go: –sys.stdout - okay for small scripts –sys.stderr - for larger programs –raise exception - in library modules let caller decide how to report! –log function - not recommended better redirect sys.stderr to log object!
32 The danger of except: What's wrong with this code: try: return self.children[O] # first child except: return None # no children Solution: except IndexError:
34 Sharing mutable objects through variables a = [1,2]; b = a; a.append(3); print b as default arguments def add(a, list=): list.append(a); return list as class attributes class TreeNode: children = ...
35 Lurking bugs bugs in exception handlers try: f = open(file) except IOError, err: print "I/O Error:", file, msg misspelled names in assignments self.done = 0 while not done: if self.did_it(): self.Done = 1
41 Measuring raw speed # Here's one way import time def timing(func, arg, ncalls=100): r = range(ncalls) t0 = time.clock() for i in r: func(arg) t1 = time.clock() dt = t1-t0 print "%s: %.3f ms/call (%.3f seconds / %d calls)" % ( func.__name__, 1000*dt/ncalls, dt, ncalls)
42 How to hand-optimize code import string, types def dictser(dict, ListType=types.ListType, isinstance=isinstance): L =  group = dict.get("main") if group: for key in group.keys(): value = group[key] if isinstance(value, ListType): for item in value: L.extend([" ", key, " = ", item, "\n"]) else: L.extend([" ", key, " = ", value, "\n"])... return string.join(L, "")
43 When NOT to optimize code Usually When it's not yet working If you care about maintainability! Premature optimization is the root of all evil (well, almost :)
45 Which API? thread - traditional Python API import thread thread.start_new(doit, (5,)) # (can't easily wait for its completion) threading - resembles Java API from threading import Thread # and much more... t = Thread(target=doit, args=(5,)) t.start() t.join()
46 Atomic operations Atomic: i = None a.extend([x, y, z]) x = a.pop() v = dict[k] Not atomic: i = i+1 if not dict.has_key(k): dict[k] = 0
47 Python lock objects Not reentrant: lock.acquire(); lock.acquire() # i.e. twice! –blocks another thread calls lock.release() No "lock owner" Solution: –threading.RLock class (more expensive)
48 Critical sections lock.acquire() try: "this is the critical section" "it may raise an exception..." finally: lock.release()
49 "Synchronized" methods class MyObject: def __init__(self): self._lock = threading.RLock() # or threading.Lock(), if no reentrancy needed def some_method(self): self._lock.acquire() try: "go about your business" finally: self._lock.release()
50 Worker threads Setup: def consumer():... def producer():... for i in range(NCONSUMERS): thread.start_new(consumer, ()) for i in range(NPRODUCERS): thread.start_new(producer, ()) "now wait until all threads done"
51 Shared work queue Producers: while 1: job = make_job() Q.put(job) Consumers: while 1: job = Q.get() finish_job(job) Shared: import Queue Q = Queue.Queue(0) # or maxQsize
52 Using a list as a queue Shared: Q =  Producers: while 1: job = make_job() Q.append(job) Consumers: while 1: try: job = Q.pop() except IndexError: time.sleep(...) continue finish_job(job)
53 Using a condition variable Shared: Q =  cv = Condition() Producers: while 1: job = make_job() cv.acquire() Q.append(job) cv.notify() cv.release() Consumers: while 1: cv.acquire() while not Q: cv.wait() job = Q.pop() cv.release() finish_job(job)