Catalog of Python Patterns? [LONG!]

Jim Dennis jimd at vega.starshine.org
Wed Apr 3 20:11:18 EST 2002


 GoF Design Patterns in Python

 [Warning LONG POST AHEAD!]

 I'd like to see a canonical catalog of OODP in Python.

 At a minimum it should list each of the 23 GoF patterns, and
 at least one minimal Python implementation of that pattern.
 It should also point out the "native" use of each pattern in
 the Python core, standard modules or among popular publicly 
 available third-party modules, packages and applications.  
 Naturally it should also discuss alternatives (Borg vs. Singleton)
 and issues (particularly cases where Pythonic idioms make 
 patterns trivial, for example).

 I realize that there used to be a "Python-Patterns" SIG (now
 "retired") and a Google search will reveal a number of articles
 on the topic (such as the one by Vespe Savikko).  Unfortunately 
 none of these sources is comprehensive.  Not one of these articles
 or web sites shows a list of all 23 GoF patterns with a minimal
 Python implementation.  Also many of them are somewhat dated.  
 I'd like to focus on Python 2.2 features (nods and caveats regarding
 older versions can be added later).

 I'll be the first to admit that I'm not a "Patterns" expert.
 That's why I'd like to see *simple* (as it brain-dead, dirt,
 simple) examples.  I'm also not an experienced professional 
 OO programmer.  I consider myself to be a student in this field.
 So all of my comments should be take with a large lump of salt,
 perhaps with a whole 5 lb. salt lick (for you equestrians).

 Here's a start.  (All I've done is copy the GoF 23 from the 
 inside cover of their book, and inserted my questions, comments 
 and suggestions.  Note that many of my comments are laced with
 the phrase "I imagine" or qualifiers like "maybe" or "could" ---
 like I said, I'm just studying this stuff and I have little
 practical experience with it).

 Who wants to contribute "text book" sample implementations?

 Creational::
   Abstract Factory:
    I've never understood the difference between an "abstract factory"
    and a "factory method."  Since Python doesn't really have 
    "abstract" versus "concrete" classes, perhaps the distinction is
    meaningless.

    Every Python class acts like a factory.  anObj = Foo() returns
    a reference to a new "Foo" object.  Additionally Python allows
    us to define normal functions which return object references.
    Indeed there is no way to tell from this assignment whether Foo
    is a class or a function.  It could easily be a function that 
    returns an object from a pool or one that returns a reference to
    a singleton. (Of course having a function masquerading as a 
    "class" in this way would inelegant and might violate the 
    principle of least surprise for some of the programmers using
    or extending this code).

    In Python2.2 it's possible to create/over-ride a class method
    with the reserved __new__ name so that we have greater control
    over the new instance creation (instantiation).  Guido shows
    how __new__ can be used to implement a Singleton. It should be
    easy to use __new__ to implement object pools (is that a 
    non-GoF pattern?), or any of creational patterns. (see below)

	Clearly, for a number of patterns, one object will be acting
	as some form of dynamic interface for one or more objects 
	"behind it."  (Decorator, Adapter, Bridge, Proxy, Facade, 
	etc).  The __init__ method (constructor) may have to instantiate
	these back-end objects, storing references to them as instance
	members.  The resulting "front-end" object must then dispatch
	method invocations (and attempted member accesses) to the 
	appropriate back-end object(s) according to the required semantics.

	Indeed this model seems to be a unifying theme among many of the
	GoF patterns.  A Decorator enhances a back-end object by adding
	it's to members or methods to it (like a wrapper) and dispatching
	"base" methods back into it's instance of the underlying object.
	Bridges and Adapters reformulate method calls as the pass them
	back.  A Proxy selectively rejects some method invocations, or
	over-rides them, to "protect" the underlying object in some way.
	A Facade allows a group of objects to appear as a single object;
	it dispatches methods to the appropriate back-end instances.
	(In any of these cases the "front-end" might also wrapper or
	over-ride the return values). 

	Quite a bit of the motivation and inspiration for the GoF 
	patterns seems to be in recognition of the limitations of 
	inheritance and class hierarchies.  They talk quit a bit about
	how these patterns allow OO programmers to avoid common
	pitfalls that result from attempts to use inheritance for 
	"has a" rather than "is a" relationships, for cases where 
	objects need to interact with other objects or in ways OTHER
	than inheritance.
	
	[Perhaps I'm wrong about this, but this is my take on the GoF
	patterns. BTW: the term "delegation" might be useful here.]

   Builder:
     I guess that "Builders" would often be used for "Composite" 
	 objects.  

   Factory Method:
     Are there any cases in Python 2.2 where this would be implemented
	 through something other than some combination of code in __new__
	 and/or __init__?

   Prototype:
     Are there any examples of this in the Python distribution?
	 I guess the implementation would usually be to create a 
	 _clone() or __clone() method which would instantiate a blank
	 object (clone = self.__class__()?) passing it appropriate 
	 constructor arguments (which it must have cached in its own
	 __init__) and calling on whatever methods are necessary to 
	 put the clone into the desired (mostly identical) state as
	 the prototypical instance.  Finally the clone() function 
	 would return a reference to the newly cloned instance.

	 I suppose one could have a Prototype (possibly some
	 selected instances of a class) define the __call__ method to
	 perform the cloning.  This would make the prototype look like
	 a factory or a normal class object throughout the rest of the
	 program.  I could envision a "Prototype" function that would
	 convert (promote?) a "normal" instance variable into a 
	 Prototype by causing it to bind its __call__ method to the
	 necessary cloning operations.  This might look a little like
	 the way that one defines a class or static method and "promotes" 
	 using syntax of the form:  mymethod = staticmethod(mymethod).
	 (Obviously the same function could be used to create a 
	 new Prototype from any extent instance: fooProto = Prototype(inst)
	 instead of myproto = Prototype(myproto); the only difference
	 is whether you bind the result to the old name or a new one).

	 One question that naturally comes up with regard to Prototypes
	 is this: How does Python's introspection features facilitate
	 the implementation of Prototypes.  How much of the cloning 
	 operation can be standardized and encapsulated in our 
	 (hypothetical) "Prototype()" function (or mix-in class?)?
	 [I can imagine all sorts of clever fiddling with self.__dict__
	 et al --- possibly using the deepcopy module/functions; but
	 the actual fiddling is beyond me at this point, so it's 
	 best left to the imagination].
	 
   Singleton:
     Alex Martelli has long been a proponent of the Borg pattern
     as an alternative to the Singleton.  While his point is well
	 taken (the focus should be on behavior, not object identity)
	 I think of the Borg as a way to work around limitation in the
	 earlier versions of Python.  With the introduction of new 
	 classes and the ability to create static methods and over-ride
	 __new__ there is no need to use the Borg. 

	 Incidentally the Borg is yet another example of the delegation
	 theme that I discussed before.  It redirects all access to 
	 any member of the collective to the "queen" (back-end) instance.

 Structural::
   Adapter:
   Bridge: 
     Frankly I don't see much difference between these two patterns.
	 I guess anygui might use Adapters to provide one uniform
	 interfacee to GUI widgets while adapting method calls into the
	 various semantics of back-end GUIs and widget collections.
	 A Bridge does the same thing.  GoF makes the distinction on 
	 "intent" (Adapters being used to access legacy or external 
	 classes through a common API, Bridges being a design decision
	 to decouple one's own classes from their APIs; it sounds like
	 hair splitting to me -- the same implementation techniques
	 should work in either case.

	 I can imagine cases where an Adapter or Bridge might have to
	 also be a bit of a Decorator or a Facade. If the semantics of 
	 one API differ from another, for example, one might have to 
	 instantiatiate additional backend objects to supply enough
	 functionality to support the desired common interface.  Otherwise
	 your interface is constrained to a least common denominator
	 among the possible back-end classes.

	 In these (and all of the other "delegation" patterns) I can
	 imagine lots of tricky, clever, subtle (and "too" extremes
	 of each: as in "too tricky," "too clever," and "too subtle")
	 ways of using __getattr__ and __setattr__ methods and the 
	 hasattr(), to avoid explicit enumeration of the back-end
	 methods for those cases where the backend does have the 
	 same interface as the Adapter/Bridge.  In other words it
	 may be possible through __getattr__ and __setattr__ or
	 similar special Python methods to allow any of these delegate
	 Patterns to only over-ride the necessary functions while
	 transparently passing along any other methods (and transparently
	 passing back results or raising exceptions?).

	 What are some pointers and caveats about this sort of "dynamic 
	 delegation" (sort of "inheritance from outside of the family 
	 tree")?
	 
   Composite:
     The GoF refers to Composites as "tree structures."  This strikes
	 me as odd since I can imagine many composites of objects that
	 aren't trees.  I can imagine chains, directed graphs, networks, 
	 and pools of objects that are interconnected through instance
	 members.  I can also see where one might have class/static 
	 members which track all of the extant instances (__new__ might
	 append to "__class__.__instancelist" and __del__ might perform
	 the remove.  Could __del__ "divert" the destruction, blank the
	 instance and add it to a pool?  Might one define a wrapper around
	 the del() built-in that would do something like: 

		def del(arg):
			if hasattr(arg,'__pool__'): arg.__pool__()
			else:                     _del(arg)

	But I gather (from reading the GoF) that the object here
	(to excuse the pun) is to allow for a method to propagate
	transparently to any subset of the composite regardless
	of whether that subset effects the whole tree (invoke it
	on the root node) or an individual leaf (the degenerate
	case).  So I guess the core of a Python Composite with
	support for a given method's propagation might be:

		Class CompositeFoo:
			def __init__(self):
				self.children = [] 
			def propagated_method(self, *args, **kwargs):
				# do stuff to self
				if supposed_to_propagate:
					for i in self.children:
						i.propagated_method(args, kwargs)

	[Does that make sense?  BTW: I'm envisioning some cases
	where the propagation is intended to be limited or conditional]

	So, what are the caveats?  Are there any examples in Python
	itself?  Are there any coding conventions that might be
    encapsulated into an abstract base class or a mix-in? 

   Decorator:
   Facade:
     As I've already said, Decorators and Facades, Adapters, Proxies
	 and Bridges (and Mediators and possibly even State objects all 
	 seem to be forms of delegation (or collaboration?).  In all 
	 cases there is a delegate (front end) object which is "used" 
	 in our code and it instantiates one or more of the underlying 
	 (back end) objects as its own member(s).  The delegate then 
	 dispatches or relays method invocations back to the underlying 
	 object(s) according the the delegate's semantics.  

	 Am I missing something here?  Is this a gross 
	 oversimplification?  Are there any differences among these
	 patterns that require special implementation (for Python)
	 or to the same language features and techniques apply to 
	 all of these "delegations" (my term).

   Flyweight:
     I've read innumerable times that the Python interpreter 
	 implements a set of static immutable objects for a small 
	 range of integers (-1 or -2 through 100 or so?).  I guess
	 this is an example of Python's native use of the Flyweight
	 pattern.  

	 Are there any others?  Would it be possible to have a 
	 sort of "copy on write" (COW) Flyweight?  These would behave
	 like Singletons for read-only purposes, but then be promoted
	 to separate/new objects on any setattr?  What would be a 
	 use for such a beast.

   Proxy:
     See my comments for Decorators, Facades, Adapters, and Bridges.


 Behavioral::
   Chain of Responsibility:
     A cascaded of Decorators or a Chain of responsibility.  
	 What's the difference.

   Command:
     Some of the GoF's comments on this sound like it would
	 be ideal for supporting AOP (aspect oriented programming).

   Interpreter:
     I just don't get this pattern.  I'll have to mull on it
	 for a bit.  Other the trite observation that Python *is*
	 an interpreter, are there any examples of the Interpreter
	 pattern in Python?

   Iterator:
     Iterators seem to be evolving into one of Pyton's most 
	 visible and commonly used features.  In Python2.2 we 
	 see automatic iteration over files, and dictionaries as
	 well as the traditional iteration over built-in sequences
	 (lists, tuples, strings).  We also see support for defining
	 iterators for our own classes.

	 It seems like iterators and generators are intertwined in
	 someway, but I don't understand it.  [So maybe I'm just
	 wrong and they only seem to be related because they're 
	 described in the same docs, or something].

   Mediator:
     This seems like a sort of bi-directional Facade to me.
	
   Memento:
     Here's where Python's "encapsulation by courtesy" and lack of
	 enforced data hiding might make a GoF pattern particularly 
	 easy; almost unecessary.  Alternatively I can imagine that 
	 some implementation of the "Memento" pattern might involve a
	 coding convention where each class that supports it implements
	 an __memoize() method.  Might that return a pickle of self?

   Observer:
	 I can imagine where some objects, including composite
	 objects might "register" with some sort of "registry" (sorry
	 for the redundancy).  I'm just not sure what sort of applications
	 one my find for this sort of "registry" even though I'm sure
	 it would be easy (almost trivial) to implement in Python.

	 I suppose such a "registry" makes quite a bit of sense in the 
	 Observer pattern.  Although I gather that this would usually be
	 a instance registry in each of the "observer/dispatchers"

   State:
     I see this as similar to an Adapter except that it may 
	 change which back end object(s) it dispatches to (or calls
	 upon or delegates for) might change dynamically during the
	 lifetime of a given instance.  For example I can imagine an
	 object that implements a simple list for some of its members
	 until the list gets beyond a given size, then it might 
	 change that to a dictionary, or some sort of AVL tree or
	 more complex data structure at the object needs to scale 
	 upwards.

   Strategy:
   Template Method:
     Would some of the mix-ins from SocketServer count as 
	 examples of the Strategy or Template Method patterns?
     
   Visitor:
     os.path.walk is an obvious example of a standard Python
	 module/function that uses Visitor.  (Obvious because it's
	 documented as the "visitor" function).

     However, this is also one of the most confusing functions
	 in the standard Python library.  I seem to see  befuddled 
	 posts about it in the c.l.py newsgroup at least once a month.
	 As the GoF point out, Visitors are often used on Composites.

	 It seems like generators might often be alternatives to 
	 Visitors.  Rather than passing a function to each object
	 and saying "do this to yourself" it seems natural to 
	 iterator over a set of object references and invoke the 
	 methods (or even, horrors! play with its attributes) directly.
	 
	 However, Python's ability to pass functions, objects and 
	 closures around as first order data types seems to make 
	 Visitors easy to implement --- if you can just understand
	 the scoping well enough for the Visitor to know what to
	 do (and who to do it to) when it gets there.




More information about the Python-list mailing list