Catalog of Python Patterns? [LONG!]
Brett Cannon
bac at OCF.Berkeley.EDU
Wed Apr 3 22:59:41 EST 2002
More information about the Python-list mailing list
Wed Apr 3 22:59:41 EST 2002
- Previous message (by thread): Catalog of Python Patterns? [LONG!]
- Next message (by thread): Catalog of Python Patterns? [LONG!]
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
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. > >
- Previous message (by thread): Catalog of Python Patterns? [LONG!]
- Next message (by thread): Catalog of Python Patterns? [LONG!]
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
More information about the Python-list mailing list