Python Patterns 1




Brandon Rhodes

PyOhio
29 July 2012

Python Patterns 1

The “1” indicates my first try
at tackling a large subject!
(I might have more
to say in the future)
Why do Python
programmers rarely talk
about design patterns?
Why do Python
programmers rarely talk
about design patterns?
In his 2009 talk at PyCon,
Joe Gregorio suggested:

The gist of this talk—

The gist of this talk

Why do Python
programmers rarely talk
about design patterns?

What is a pattern?

Two books…

A Pattern Language
Christopher Alexander
(1977)
Design Patterns—
Elements of Reuseable
Object-Oriented Software
Gamma, Helm, Johnson, Vlissides
“The Gang of Four”
(1995)
Not all patterns are
object-oriented design patterns
OO Design Patterns  ⊂  Patterns
Patterns in Python exist
at many different levels

Small procedural pattern

if 'owner' in roledict:
    owner = roledict['owner']
else:
    owner = admin
(-1) for looking up 'owner' twice
(-1) requires four lines and an indent
owner = roledict.get('owner', admin)

Bigger procedural pattern

for item in items:
    if is_for_sale(item):
        cost = compute_cost(item)
        if cost <= wallet.money:
            buy(item)
(-1) actual buy() is buried 2 levels deep
for item in items:
    if not is_for_sale(item):
        continue
    cost = compute_cost(item)
    if cost > wallet.money:
        continue
    buy(item)
Object-Oriented design patterns
are specifically about putting the
big pieces of an application together
How can a language
feature replace a pattern?

1st principle in Design Patterns (p18):

“Program to an interface,
not an implementation.”
Problem: static typing locks you in
(there is only one class named File)
public void writeZen(File out) {
    out.write('Beautiful is better than ugly.');
}
Solution: do not mention actual types
(any class can implement IWritable)
public void writeZen(IWritable out) {
    out.write('Beautiful is better than ugly.');
}

Why would you do this?

Because someday you might want to write
to something that is file-like
but not an actual File!
public void writeZen(File out) {
    out.write('Beautiful is better than ugly.');
}
Only usable for File subclasses
If your new WebSocket class
also offers IWritable then you
can re-use this method for the web!
public void writeZen(IWritable out) {
    out.write('Beautiful is better than ugly.');
}
So, do we worry
about this in Python?

No

Why?

Because in Python we
throw static typing overboard
and instead use duck typing
def writeZen(out):
    out.write('Beautiful is better than ugly.')
This function accepts anything
and simply risks an exception
if the duck refuses to quack
So a language feature—
“Python is dynamic”
and does not make you pre-specify
the types accepted by a function
—eliminates a whole GoF design principle!

In other words—

All Python code you have ever written
automatically adheres to the GoF principle
that functions should not name the
classes that can be passed in.
(Unless you add inflexibility
manually with isinstance() tests)

In general

Java and C++ programmers
maintain grueling and expensive
coding disciplines
to remove a set of handcuffs
that in Python quite simply
do not exist to begin with

Opinion

Is it a damning indictment of C++/Java
that their most fundamental and
persistent coding practices
all seem designed to make Java
more like writing in Python?

So,

we have seen how a Gang of Four
principle disappears in Python.
What about the patterns themselves?

The 5 Creational Patterns

Python is brilliant

No separate syntax for
object instantiation
# C++, Java, JavaScript

v = getvalue();   // plain function
t = new Thing();  // instantiation
# Python

t = getthing()    # everything!
In other languages the new keyword
commits you to doing a real instantiation,
which makes all kinds of things impossible:
def get_talker(s):
    if s.startswith('xmlrpc://'):
        return XMLRPC_Talker(s)
    else:
        return JSON_Talker(s)
So most “creational patterns”
are contortions to avoid making
brittle new calls in your C++/Java code
In Python, you cannot tell
what is being called:
t = thing()
Is this a bound method?
A class with a lowercase name?
Or a plain old function?
It can be whatever
its author needed!
t = thing()
isinstance(t, thing)
(You can tell the difference if you
isinstance() with the constructor —
but you wouldn't do that, right?)

Notes about Python singletons

  1. Using a global variable is bad,
    because you can never switch in
    something dynamic: mymodule.foo
    can not be intercepted!

Notes about Python singletons

  1. So you should always make callers
    actually invoke a function to get
    your singleton; minimally:
    # A kind-of singleton
    
    _singleton = MyClass()
    def get_singleton():
        return _singleton
    

Notes about Python singletons

  1. But note that the real Singleton
    pattern is supposed to let the
    caller provide a subclass!
_singleton = None

def get_singleton(cls=MyClass):
    global _singleton
    if _singleton is None:
        _singleton = cls()
    return _singleton
You can always make things
more complicated later:
What if your users want a constructor
that is also the singleton's class,
so they can isinstance() it?
Then you have to roll up your sleeves
and give your class a __new__() method,
see Stack Overflow for examples
(and arguments against!)

Prototype

Another creational pattern is provided
not by the Python language itself,
but by the Standard Library:
The copy module can introspect
and make a copy of an object

Builder

The most complex creational
pattern is actually very useful:
a Builder receives instructions about
what to build, and hides the details
of the instances it links together

Making XML without a builder

We see the gory details
as instances are created
and then linked together
from lxml import etree

root = etree.Element('body')
h1 = etree.Element('h1')
h1.text = 'The Title'
root.append(h1)
p = etree.Element('p')
p.text = 'Always write Python'
root.append(p)

Making XML with a builder

Our code does not become laden
with any information about the
classes being instantiated!
from lxml.builder import E

doc = E('body',
        E('h1', 'The Title'),
        E('p', 'Always write Python'))

Builder

This pattern perhaps appears
in popular frameworks like lxml
more often than in user code

Creational Patterns in Python

Designed away by language:

Abstract Factory
Factory Method

Trivial:

Singleton
Prototype

Useful:

Builder

The 7 Structural Patterns

Structural Pattern: Adapter

Adapter

Very important!
Wraps one class
so that it behaves
like another class

Adapter example

Sockets do not read
and write like files do
>>> import socket
>>> s = socket.socket()
>>> s.read
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: '_socketobject' object
 has no attribute 'read'
Instead, they “send” and “receive”
>>> s.send
<built-in method send>
>>> s.recv
<built-in method recv>
So the Standard Library provides
a socket._fileobject adapter that
translates file-like read() and write()
operations into send() and recv()
calls on the socket
>>> f = s.makefile()
>>> f.read
<bound method _fileobject.read>
Adapters are a crucial pattern
letting us re-use code in Python
Sometimes the day can be saved by
putting an object behind a file-like or
sequence-like adapter so it can be
passed to existing Python code

Structural Pattern: Bridge

Bridge

Solution: another layer!

Have one class that translates
the Plain and Bordered ideas
into primitive window operations
Then
Have another class that can
perform these operations under
Windows, Mac, and Linux
This principle actually applies
at many levels in Python!

Gary Bernhardt at PyCon 2012

Q: My models are full of business
logic — how can I write simple tests
that don't write to the database?
class Person(Database_Model):
    def rename(self, new_name): ...
    def direct_deposit(self, amount): ...

Gary Bernhardt at PyCon 2012

A: Have models that do nothing but
persist information to storage
and then
write a business logic layer
that implements the operations
that were expressed in methods
The Bridge pattern is actually a
good idea even at the level of modules
My modules often tackle only
a single level of impedance
match, instead of trying
to take several steps
# foo.py
CherryPy plugin  thread  library
# foo.py
CherryPy plugin  stdlib threading

# bar.py
thread  library

This leads to a Brandon rule:

If a module imports three major libraries
or frameworks, see if you can refactor it
into two modules, each of which only
imports two libraries
Your main program can then
plug the pieces together in main()

Structural Pattern: Composite

A class whose instances
are designed to lock
together in a tree

Composition is a common pattern

Two common mechanisms:

# Store children in attributes

obj.attr = subobject

# Store a list or dict of children

obj.children = [subobject, ...]

Structural Pattern: Facade

An object that hides a complex
tree or network of other objects

Structural Pattern: Flyweight

Flyweight

A flyweight is a small object
that is immutable and can be
re-used in many contexts
A text box implemented without flyweights
textbox.chars = [c1, c2, c3, ...]

c1.letter = 'T'
c1.width = 9
c1.height = 10
c1.depth = 0
c1.outline = [(0, 10), (9, 10), ...]
c1.x = 0
c1.y = 14
c1.draw()

c2.letter = 'h'

c3.letter = 'e'

You have to create n instances
of the letter “T” so that each can
occupy a different x and y
c1.letter = 'T'
c1.width = 9
c1.height = 10
c1.depth = 0
c1.outline = [(0, 10), (9, 10), ...]
c1.x = 0
c1.y = 14
c1.draw()
The flyweight pattern moves this
per-object state out into the
parent or larger context
textbox.chars = [(cT, 0, 14),
                 (ch, 9, 14),
                 (ce, 18, 18),
                 (c_space, 27, 14),
                 (ch, 36, 14),
                 (ce, 45, 18),
                 ...]

cT.letter = 'T'
cT.width = 9
cT.height = 10
cT.depth = 0
cT.outline = [(0, 10), (9, 10), ...]
cT.draw(x, y)

Flyweight

Flyweight

C-Python uses the flyweight pattern
internally for integer objects
Since integer objects are immutable,
it only keeps a single copy of each small
integer like 0, 1, 2, ... and hands them
to your code over and over again

Structural Pattern: Proxy

Proxy

Proxy

Structural Pattern: Decorator

Decorator

So there you have it

Note that none of the patterns
disappeared or proved useless simply
because our language is Python

Structural Patterns

The 11 Behavioral Patterns

Behavioral Pattern: Chain of responsibility

Behavioral Pattern: Command

Replaces immediate actions like:

paintLine(x1, y1, x2, y2)

Instead, you instantiate a command:

cmd = PaintLineCommand(x1, y1, x2, y2)
cmd.do()
command_history.append(cmd)

Behavioral Pattern: Interpreter

Instead of compiling to machine language,
an interpreted language gets parsed into a
data structure that an interpreter program
iterates across, following the instructions

Behavioral Pattern: Iterator

Two basic ways to implement iterator
First, you can create an actual class
to be your iterator that remembers:
  1. What it is iterating across
  2. Where it is in the sequence

Iterator

class Box(object):
    def __init__(self): self.things = [10, 20, 30]
    def __iter__(self): return BoxIterator(self)

class BoxIterator(object):
    def __init__(self, box):
        self.box = box
        self.index = -1

    def next(self):
        self.index += 1
        if self.index >= len(self.box.things):
            raise StopIteration()
        return self.box.things[self.index]
Or, you can simply make
__iter__() a generator
class Box(object):
    def __init__(self):
        self.things = [10, 20, 30]

    def __iter__(self):
        for thing in things:
            yield thing

Iterator

# JavaScript is littered with:
for (var i=0; i < box.length; i++) ...

Behavioral Pattern: Mediator

Behavioral Pattern: Memento

Behavioral Pattern: Observer

Without Observer pub-sub, you get:

class MyModel(...):
    def set_total(self, number):
        self.total = number
        self.titlebar.update()
        self.graph.update()
        self.summary.update()

With Observer pub-sub, things are simpler:

class MyModel(...):
    def set_total(self, number):
        self.total = number

class MyTitlebar(...):
    def __init__(self, model):
        subscribe(model, 'total', self.redraw)


Observer rarely appears in Python
for reasons we will discuss shortly

Behavioral Pattern: State

        open()    close()
TCPStart  TCPOpen  TCPClosed

What we want to avoid:

if state == 'start':
    if action == 'open':
        
    elif action == 'close':
        
elif state == 'open':
    if action == 'open':
        
    elif action == 'close':
        

Behavioral Pattern: State

        open()    close()
TCPStart  TCPOpen  TCPClosed

Behavioral Pattern: Strategy

Behavioral Pattern: Template

Behavioral Pattern: Visitor

Behavioral Patterns

Conclusions

Bonus Round

Dependency Injection

The Problem

def a(): ... b() ...
def b(): ... c() ...
def c(): pass

The Problem

A simple, concrete example:

def google_search(query_text):
    
    urllib.urlopen(url)
    

Dependency Injection

DI says we can pass the callable
that performs the “next action down”:
def google_search(query_text, urlopen):
    
    response = urlopen(url)
    

Problem

This can get annoying

big_function(
    query_text,
    urlopen=urllib.urlopen,
    listdir=os.listdir,
    stat=os.stat,
    link=os.link,
        
    ):
    
Grouping dependent functions into
objects can make it a bit simpler
big_function(
    query_text,
    url_utils,
    os_utils,
    ):
    
But can we avoid passing so
many pieces to begin with?

Another Approach

“Return of Control”
But what if you just did this?
def google_search(query_text):
    
    return url

def google_parse(response):
    
    return result

def main():
    url = google_search(query_text)
    response = urlopen(url))
    result = google_parse(response)
This can result in simple functions
and methods that are easy to test
and simple top-level code
that simply assembles Lego pieces
and then sets them running

Guidelines

End of Bonus Round

So, Patterns

Most patterns are still
a good idea in Python
Why don't we use them more?

My claim:

We rarely meet the bigger patterns
not because of any feature
or magic of the Python langauge
but
because of the kind of application
that Python programmers tend to write

Reason #1

Python programs tend to perform
small
lightweight
one-shot
tasks
At the end of each of these tasks,
you throw away your objects and start over
This is the Unix philosophy
of small targeted tools

Reason #2

When you do write
a large complex long-running
application in Python, your framework
already implements the relevant patterns

Again:

We rarely meet the bigger patterns
not because of any feature
or magic of the Python langauge
but
because of the kind of application
that Python programmers tend to write
We tend to write glue
because the Open Source community
have done the hard parts for us!

Thank you!

Some earlier work: