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 patterns are built in” to Python
- Concurrent patterns need more attention
- Missing patterns could be new features
The gist of this talk—
The gist of this talk
Why do Python
programmers rarely talk
about design patterns?
- Built-in language features
- Are provided by frameworks
- We avoid big applications
What is a pattern?
Two books…
Christopher Alexander
(1977)
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
- Classes
- Objects
- References
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
“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?
- 5 Creational Patterns
- 7 Structural Patterns
- 11 Behavioral Patterns
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:
- Returning another class
- Always return the same instance
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
Returning another class
(Factory Method, Abstract Factory)
Always return the same instance
(Singleton)
In Python, you cannot tell
what is being called:
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
Using a global variable is bad,
because you can never switch in
something dynamic: mymodule.foo
can not be intercepted!
Notes about Python singletons
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
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:
- Hide _singleton inside a closure
- Hide _singleton inside a class
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:
Useful:
The 7 Structural Patterns
Structural Pattern: Adapter
Adapter
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
Says not to use subclassing for
two separate purposes in one class
-
MacPlainWindow MacBorderedWindow
WinPlainWindow WinBorderedWindow
LinuxPlainWindow LinuxBorderedWindow
Partly rationalized by limitations
of C++ (no mixins, much subclassing)
Solution: another layer!
Have one class that translates
the Plain and Bordered ideas
into primitive window operations
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
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
- email.Message can have messages inside
- lxml.etree.Element lists child elements
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
An etree Element lets you root.find()
and root.iter() which traverse the entire
tree of nodes for you
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
A small set of objects can each
appear thousands of times inside
of parent objects
But context like the x and y passed
to Character.draw(x, y) will now need
to be passed in to flyweight methods
Saves memory at the expense of noise
and higher coupling
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
A proxy object wraps another object called
the subject and accepts method calls and
attribute lookups on its behalf
Proxying can be performed dynamically
in Python with __getattr__() instead of
having to write n proxying methods
Proxy
weakref.proxy(obj) returns a proxy
Remote procedure call libraries offer
proxies for remote APIs, including the
Standard Library xmlrpc.client.ServerProxy
and also Pyro and rpyc
The Zope web framework used proxies
that enforced security
Structural Pattern: Decorator
Decorator
Like the Proxy, a Decorator class
offers the same attributes and methods
as the subject that it wraps
But instead of being completely
transparent, it varies the behavior
or edits the data passing in and
out of the subject
Decorators tend to appear in “glue”
code in applications, not inside
Python libraries themselves
Note that none of the patterns
disappeared or proved useless simply
because our language is Python
Structural Patterns
- Adapter
- Bridge
- Composite
- Decorator
- Facade
- Flyweight
- Proxy
The 11 Behavioral Patterns
Behavioral Pattern: Chain of responsibility
Pass a request along a chain of objects
until one of them decides to handle it
def processClick(self, x, y):
if self.active:
self.buttonPress(x, y)
else:
self.next.processClick(x, y)
Used by GUIs and DOMs for mouse events
Can be used by “help” feature: maybe button
has no help, but enclosing form does
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)
- Allows auditing and undo()
- Crucial to version control systems
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
Data structure can be AST or bytecode
Python itself is interpreted!
Small domain-specific languages do sometimes
get written in Python to ease customization
Behavioral Pattern: Iterator
Built-in because Python is awesome
Introduced in 2001 — the most important
Python innovation of decade
See Raymond Hettinger's PyCodeConf 2011
talk “What Makes Python Awesome?”
Lets you for x in obj: across your own
user-defined obj, or loop by hand
with i = iter(obj) and i.next()
Two basic ways to implement iterator
First, you can create an actual class
to be your iterator that remembers:
- What it is iterating across
- 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
-
Eliminates ugly mechanics of iteration
so code is more readable
Enhances re-use because code is
easy to lock together in new ways;
again, see Raymond's talk!
# JavaScript is littered with:
for (var i=0; i < box.length; i++) ...
Behavioral Pattern: Memento
A memento is a record of an object's
internal state — might be a string, or
file, or complex data structure
Callers ask an object instance for a
memento, then can hand it back later
to ask the object to restore itself
to its earlier state
Similar to pickle but instead of
creating a new object like un-pickling,
it changes an existing object
Behavioral Pattern: Observer
Very popular pattern in GUIs and DOMs
Display elements let the framework
know that they need to redraw when
specific model attributes change
Your models stay simple and have
no knowledge of the big application
you have build around them, so long
as they signal a list of listeners
when an attribute changes
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
Represent each state as a class
like TCPOpen or TCPClosed
Give each class one method for
each transition like open()
and close() that returns the
next state
Avoids huge nested if-then's, but
simple state machines can be a dict
in Python instead
Behavioral Pattern: Strategy
Parametrize a big process by passing in
an object that specifies custom behavior
Python has first-class functions so we
often just pass callbacks instead
A simple example of the Strategy pattern
is the key= function that we pass to
sorted() and list.sort()
Behavioral Pattern: Template
Give your classes empty methods like
aboutToStart() and justStarted()
that a subclass can customize
The original author of threading in
the Standard Library intended callers to
subclass (!) Thread and provide their
own, more interesting run() method
In Python we often pass a callable instead,
or — when there are many callbacks — pass
a Strategy object instead
Behavioral Pattern: Visitor
Replaces doThis() and doThat() methods on
tree nodes, that act on the current node and
then call the same method on their children,
with a do(action) traversal method
Not frequently encountered in Python
We tend to turn this problem inside out:
for node in lxml_root.walk(): # or os.walk!
# do something to the node
Behavioral Patterns
Are big solutions for big problems
Most patterns work fine in Python
Many of them turn up in big and
popular libraries, or even in the
Standard Library
The Iterator pattern is now a
foundation of how we write Python
Conclusions
Creational patterns do tend to partly
disappear in Python
But the Structural patterns are often
good ways to structure your code
Most Behavioral patterns remain useful
but some can be collapsed into callbacks
or data structures that hold functions
Bonus Round
Dependency Injection
The Problem
def a(): ... b() ...
def b(): ... c() ...
def c(): pass
Hard to re-use a() in situations
where you want to do something else
instead of doing b()
Hard to test a() in isolation
Expensive to test a() without
Foord's mock.patch() if b() or c()
does something expensive like talking
to a database or writing a file
The Problem
A simple, concrete example:
def google_search(query_text):
⋮
urllib.urlopen(url)
⋮
- Hard to test
- Hard to re-use
Dependency Injection
DI says we can pass the callable
that performs the “next action down”:
def google_search(query_text, urlopen):
⋮
response = urlopen(url)
⋮
- In production, pass urllib.urlopen
- In tests, just pass a fake function
- Easy to re-purpose in the future
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
I do this to application subsystems,
but I do not make the entire
application a huge function that
thinks dozens of pieces together!
I often pass my own subsystems as
parameters, but give myself permission
to use Standard Library calls without
insisting on DI — Foord mock FTW
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
because of the kind of application
that Python programmers tend to write
Reason #1
Python programs tend to perform
small
lightweight
one-shot
- Answering a web request
- Transforming a text file
- Searching through log files
- Operate on a database table
- Doing a Hadoop reduce() step
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
- Pyglet
- PyGame
- Tkinter
- PySide / PyQt
- Twisted
Again:
We rarely meet the bigger patterns
not because of any feature
or magic of the Python langauge
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:
- Vespe Savikko (1997) (academic)
- Bruce Eckel (2001) (unfinished)
- Greg Sullivan (2002) (not Python)
- Alex Martelli (2007)