Catalog of Python Patterns? [LONG!]

Brett Cannon bac at OCF.Berkeley.EDU
Wed Apr 3 22:59:41 EST 2002


Have you looked at the Python Cookbook
(http://aspn.activestate.com/ASPN/Cookbook/Python)?  There are several GoF
patterns already there and done by some of the top Python programmers out
there.  There are even some Python-skewed version of some of these
patterns (the Borg pattern by Alex Martelli comes to mind).

-Brett C.



On 4 Apr 2002, Jim Dennis wrote:

>
>  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