Python Design Pattern
Python Design Pattern
Ri
2
Patterns
...every building, every town, is made of certain entities which I call patterns... in terms of these pattern languages, all the different ways ... become similar in general outline.
5
Design Patterns in SW
rich, thriving culture and community mostly a subculture of OO development Gamma, Helms, Johnson, Vlissides (1995) "the gang of 4" AKA "Gof4" PLOP conferences & proceedings thereof DP once risked becoming a fad, or fashion there is no silver bullet... ...but, we now know, DP are here to stay ...and, NOT independent from the choice of programming language!-)
8
DP write-up components
NAME, context, problem forces, solution, examples results, rationale, related DPs KNOWN USES (KU for short) DP are discovered, NOT invented DP are about description (and helpful related suggestions), NOT prescription formal xed schema not a must but helpful as a checklist somewhat audience-dependent
10
SW DP Books
11
Categories of SW DP
Creational concern the ways and means of object instantiation Structural deal with the mutual composition of classes or objects Behavioral analyze the ways in which classes or objects interact and distribute responsibilities among them
13
Prolegomena to SW DPs
"program to an interface, not to an implementation" usually done "informally" in Python "favor object composition over class inheritance" in Python: hold, or wrap inherit only when it's really convenient very direct way to expose all methods in the base class (reuse + usually override + maybe extend) but, it's a rather strong coupling!
14
15
Wrapping to "restrict"
class RestrictingWrapper(object): def __init__(self, w, block): self._w = w self._block = block def __getattr__(self, n): if n in self._block: raise AttributeError, n return getattr(self._w, n) ...
Creational Patterns
not very common in Python... ...because "factory" is essentially built-in!-)
18
Singleton ("Highlander")
class Singleton(object): def __new__(cls, *a, **k):! if not hasattr(cls, '_inst'): cls._inst = super(Singleton, cls ).__new__(cls, *a, **k) return cls._inst
Monostate ("Borg")
class Borg(object): _shared_state = {} def __new__(cls, *a, **k): obj = super(Borg, cls ).__new__(cls, *a, **k) obj.__dict__ = cls._shared_state return obj
Factories in Python
each type/class is intrinsically a factory internally, may have __new__ externally, it's just a callable, interchangeable with any other may be injected directly (no need for boilerplate factory functions) modules can be kinda "abstract" factories w/o inheritance ('os' can be 'posix' or 'nt')
23
KU: type.__call__
def __call__(cls,*a,**k): nu = cls.__new__(cls,*a,**k) if isinstance(nu, cls): cls.__init__(nu,*a,**k) return nu (An instance of "two-phase construction")
24
factory-function example
def load(pkg, obj): m = __import__(pkg,{},{},[obj]) return getattr(m, obj) # example use: # cls = load('p1.p2.p3', 'c4')
25
Structural Patterns
The "Masquerading/Adaptation" subcategory: Adapter: tweak an interface (both class and object variants exist) Facade: simplify a subsystem's interface Bridge: let many implementations of an abstraction use many implementations of a functionality (without repetitive coding) Decorator: reuse+tweak w/o inheritance Proxy: decouple from access/location
26
Adapter
client code ! requires a protocol C supplier code " provides different protocol S (with a superset of C's functionality) adapter code # "sneaks in the middle": to !, # is a supplier (produces protocol C) to ", # is a client (consumes protocol S) "inside", # implements C (by means of appropriate calls to S on ")
ain protocol C
27
Toy-example Adapter
C requires method foobar(foo, bar) S supplies method barfoo(bar, foo) e.g., " could be: class Barfooer(object): def barfoo(self, bar, foo): ...
28
Object Adapter
per-instance, with wrapping delegation: class FoobarWrapper(object): def __init__(self, wrappee): self.w = wrappee def foobar(self, foo, bar): return self.w.barfoo(bar, foo) foobarer=FoobarWrapper(barfooer)
29
Class Adapter
per-class, w/subclasing & self-delegation: class Foobarer(Barfooer): def foobar(self, foo, bar): return self.barfoo(bar, foo) foobarer=Foobarer(...w/ever...)
30
Adapter KU
socket._leobject: from sockets to le-like objects (w/much code for buffering) doctest.DocTestSuite: adapts doctest tests to unittest.TestSuite dbhash: adapt bsddb to dbm StringIO: adapt str or unicode to le-like shelve: adapt "limited dict" (str keys and values, basic methods) to complete mapping via pickle for any <-> string + UserDict.DictMixin
31
Adapter observations
some RL adapters may require much code mixin classes are a great way to help adapt to rich protocols (implement advanced methods on top of fundamental ones) Adapter occurs at all levels of complexity in Python, it's _not_ just about classes and their instances (by a long shot!-)
32
Facade
supplier code " provides rich, complex de" functionality in protocol S ier code provides rich,subset C of S we!need simple tionality in protocol facade codeS$ implements and supplies C mpler "subset" C ofof S appropriate calls to S on ") (by means
17
STRAKT
33 21
Facade vs Adapter
Adapter's about supplying a given protocol required by client-code or, gain polymorphism via homogeneity Facade is about simplifying a rich interface when just a subset is often needed Facade most often "fronts" for many objects, Adapter for just one
34
Without Facade
With Facade
http://www.tonymarston.net/php-mysql/design-patterns.html
35
Facade KU
asynchat.fo facades for list dbhash facades for bsddb ...also given as example of Adapter...!-) old sets.Set used to facade for dict Queue facades for deque + lock well...:-) os.path: basename, dirname facade for split + indexing; isdir &c facade for os.stat + stat.S_ISDIR &c.
36
Facade observations
some RL facades may have substantial code simplifying the _protocol_ is the key interface simplications are often accompanied by minor functional additions Facade occurs at all levels of complexity, but it's most important for _complicated subsystems_ (and reasonably-simple views) inheritance is never useful for Facade, because it can only "widen", never "restrict" (so, wrapping is the norm)
37
Adapting/Facading callables
callables (functions &c) play a large role in Python programming -- so you often may need to adapt or facade them functools.partial to preset some args (AKA "currying") + bound-methods special case decorator syntax to adapt functions or methods by wrapping in HOFs closures for simple HOF needs classes w/__call__ for complex ones
38
Bridge
have N1 realizations ! of abstraction A, each using any one of N2 implementations " of functionality F, without coding N1*N2 cases ... have abstract superclass A of all ! hold a reference R to the interface F of all ", ensure each ! uses any functionality of F (thus, from a ") only by delegating to R
39 21
40
Decorator
client code ! requires a protocol C supplier code " does provide protocol C but we want to insert some semantic tweak often dynamically plug-in/plug-out-able decorator code % "sneaks in the middle": ! uses % just as it would use " % wraps ", and it may intercept, modify, add (a little), delegate, ...
42
KU of Decorator
gzip.GzipFile decorates a le with compress/decompress functionality threading.RLock decorates thread.Lock with reentrancy & ownership functionality codecs classes decorate a le with generic encoding and decoding functionality
44
Proxy
client code ! needs to access an object & however, something interferes w/that...: & lives remotely, or in persisted form access restrictions may apply (security) lifetime or performance issues proxy object ! "sneaks in the middle": ! wraps &, may create/delete it at need may intercept, call, delegate, ... ! uses ! as it would use &
45
KU of Proxy
the values in a shelve.Shelf proxy for persisted objects (get instantiated at need) weakref.proxy proxies for any existing object but doesn't "keep it alive" idlelib.RemoteDebugger uses proxies (for frames, code objects, dicts, and a debugger object) across RPC to let a Python process be debugged from a separate GUI process
47
Q & A on part 1
Q? A!
48
Behavioral Patterns
Template Method: self-delegation "the essence of OOP"... State and Strategy as "factored out" extensions to Template Method
49
Template Method
great pattern, lousy name "template" very overloaded generic programming in C++ generation of document from skeleton ... a better name: self-delegation directly descriptive TM tends to imply more "organization"
50
Classic TM
abstract base class offers "organizing method" which calls "hook methods" in ABC, hook methods stay abstract concrete subclasses implement the hooks client code calls organizing method on some reference to ABC (injecter, or...) which of course refers to a concrete SC
51
TM skeleton
class AbstractBase(object): def orgMethod(self): self.doThis() self.doThat() class Concrete(AbstractBase): def doThis(self): ... def doThat(self): ...
52
53
AbstractPager
class AbstractPager(object): def __init__(self, mx=60): self.mx = mx self.cur = self.pg = 0 def writeLine(self, line): if self.cur == 0: self.doHead(self.pg) self.doWrite(line) self.cur += 1 if self.cur >= self.mx: self.doFoot(self.pg) self.cur = 0 self.pg += 1
54
55
Classic TM Rationale
the "organizing method" provides "structural logic" (sequencing &c) the "hook methods" perform "actual ``elementary'' actions" it's an often-appropriate factorization of commonality and variation focuses on objects' (classes') responsibilities and collaborations: base class calls hooks, subclass supplies them applies the "Hollywood Principle": "don't call us, we'll call you"
57
Default implementations often handier, when sensible; but "mandatory" may be good docs.
58
Overriding Data
class AbstractPager(object): mx = 60 ... class CursesPager(AbstractPager): mx = 24 ...
access simply as self.mx -- obviates any need for boilerplate accessors self.getMx()...
59
KU: Queue.Queue
class Queue: ... def put(self, item): self.not_full.acquire() try: while self._full(): self.not_full.wait() self._put(item) self.not_empty.notify() finally: self.not_full.release() def _put(self, item): ...
60
Queues TMDP
Not abstract, often used as-is thus, implements all hook-methods subclass can customize queueing discipline with no worry about locking, timing, ... default discipline is simple, useful FIFO can override hook methods (_init, _qsize, _empty, _full, _put, _get) AND... ...data (maxsize, queue), a Python special
61
Customizing Queue
class LifoQueueA(Queue): def _put(self, item): self.queue.appendleft(item) class LifoQueueB(Queue): def _init(self, maxsize): self.maxsize = maxsize self.queue = list() def _get(self): return self.queue.pop()
62
KU: cmd.Cmd.cmdloop
def cmdloop(self): self.preloop() while True: s = self.doinput() s = self.precmd(s) f = self.docmd(s) f = self.postcmd(f,s) if f: break self.postloop()
63
KU: asyncore.dispatcher
# several organizing-methods, e.g: def handle_write_event(self): if not self.connected: self.handle_connext() self.connected = 1 self.handle_write()
64
65
TM in DictMixin
class DictMixin: ... def has_key(self, key): try: # implies hook-call (__getitem__) value = self[key] except KeyError: return False return True def __contains__(self, key): return self.has_key(key) ...
66
Exploiting DictMixin
class Chainmap(UserDict.DictMixin): def __init__(self, mappings): self._maps = mappings def __getitem__(self, key): for m in self._maps: try: return m[key] except KeyError: pass raise KeyError, key def keys(self): keys = set() for m in self._maps: keys.update(m) return list(keys)
67
TM + introspection
"organizing" class can snoop into "hook" class (maybe descendant) at runtime nd out what hook methods exist dispatch appropriately (including "catchall" and/or other error-handling)
69
KU: cmd.Cmd.docmd
def docmd(self, cmd, a): ... try: fn = getattr(self, 'do_' + cmd) except AttributeError: return self.dodefault(cmd, a) return fn(a)
70
Interleaved TMs KU
plain + factored + introspective multiple "axes", to separate carefully distinct variabilities a DP equivalent of a "Fuga a Tre Soggetti" "all art aspires to the condition of music" (Pater, Pound, Santayana...?-)
71
KU: unittest.TestCase
def__call__(self, result=None): method = getattr(self, ...) try: self.setUp() except: result.addError(...) try: method() except self.failException, e:... try: self.tearDown() except: result.addError(...) ...result.addSuccess(...)...
72
73
Strategy DP
class Calculator(object): def __init__(self): self.strat = Show() def compute(self, expr): res = eval(expr) self.strat.show('%r=%r'% (expr, res)) def setVerbosity(self, quiet=False): if quiet: self.strat = Quiet() else: self.strat = Show()
74
Strategy classes
class Show(object): def show(self, s): print s class Quiet(Show): def show(self, s): pass
75
76
State classes
class Show(object): def show(self, s): print s def setVerbosity(self, obj, quiet): if quiet: obj.state = Quiet() else: obj.state = Show() class Quiet(Show): def show(self, s): pass
77
Ring Buffer
FIFO queue with nite memory: stores the last MAX (or fewer) items entered good, e.g., for logging tasks intrinsically has two macro-states: early (<=MAX items entered yet), just append new ones later (>MAX items), each new item added must overwrite the oldest one remaining (to keep latest MAX items) switch from former macro-state (behavior) to latter is massive, irreversible
78
79
80
Switching a method
class RingBuffer(object): def __init__(self): self.d = list() def append(self, item): self.d.append(item) if len(self.d) == MAX: self.c = 0 self.append = self.append_full def append_full(self, item): self.d.append(item) self.d.pop(0) def tolist(self): return list(self.d)
81
Q & A on part 2
Q? A!
82