"""Sketches of assorted things one might find under an interpreter's bonnet. Lookups are documented in python.py; the primitive pnamespace(lookup) is here presumed, yielding an `object' whose attribute-lookup functionality is implemented by calling the given lookup: getattr(pnamespace(lookup), key) is synonymous with lookup(key). Contents: mason -- archetype (i.e. typedef documentation) for builders. public(name) -- test whether name should appear in __dir__. aslookup(obj) -- represents its input as a lookup; `inverse' of pnamespace. within -- a very primitive builder. A builder is a callable taking arbitrary arguments and returning a twople, the first of which I call `the built object', the second `the suite wrapper'. The suite wrapper will typically borrow attributes off the built object and support attribute modification behaviour which changes the built object; if the built object supports attribute modification, this need not coincide with that of its suite wrapper. A build statement, generalising python's class statement, has the form: builder name opt-tuple `:' suite The opt-tuple has an implicit default of (). Execution of the build statement proceeds as follows: * the builder and opt-tuple, if any, are evaluated * the builder is called with the opt-tuple as positional arguments and, possibly, some implementation-defined keyword arguments, whose names (if any) shall be magic (i.e. begin and end with __), with values provided by the parser or (possibly) the interperter; c.f. the __name__ and __module__ attributes one can find on python classes. * the builder returns its (object, wrapper) twople * the built object is assigned to the given name (in the context in which the build statement was encountered), as if by name = object. * the suite is evaluated in the namespace of the suite-wrapper, i.e. name lookup in the suite means attribute lookup on the wrapper, while assignment (or del) is implemented by the __setattr__ (or __delattr__) of the wrapper. Once the suite has been executed, the suite wrapper is no longer available (except to any `methods' of the built object). Note that the built object is bound in the surrounding namespace *before* the suite is executed. I presume that a build statement appearing in the suite of a function definition uses the locals of the function as its suite's globals; otherwise it appears in the suite of a build statement and the inner suite uses the globals of the outer as globals. These last will be the locals of: either the function during whose evaluation; or the module during whose initialisation; the suite is executed. I presume an extension to the python grammar's handling of * and ** parameters in def statements: see python.html and related for details. Roughly: * Only positional arguments can affect a parameter to the right of the ** parameter. * Only keyword arguments can affect a parameter to the right of the * parameter. * Any parameter to the right of both * and ** parameters is a `pure tunnel', i.e. it cannot be overwritten by any argument, so it must have a default; it serves to make the default (computed when the function is defined) available to each execution of the function. * As in python, all positional arguments precede all keyword ones. (The same might not need to be true for parameters, e.g.: def ready(steady='yes', *, go): pass can only be invoked with a go=value argument; without, you should get a TypeError. However, I don't see this as desirable; just incidental.) * If the ** parameter has no name, keyword arguments can only use names appearing to the left of the ** parameter (not including any name the * parameter may have). * Otherwise (as in python), any keyword arguments not allowed in the previous case are collected together into a dictionary made available to the function via the name of the ** parameter. * If the * parameter has no name, no more positional arguments may be given than there are parameters to the left of the * parameter (not including the ** parameter). * Otherwise, the surplus positional arguments are collected together into a tuple made available (as in python) via the name of the * parameter. $Id: bonnet.py,v 1.6 2005/02/12 17:39:46 eddy Exp $ """ # typedef ... def mason(*bases, **raw): """The generic interface to builders. A builder takes arbitrarily many positional arguments. These are archetypically bases of the object built - it borrows attributes from them - but they're whatever entries were supplied in the bases-tuple of the build statement. A builder takes arbitrary keyword arguments. The archetypical keyword argument would be supplied by the parser (c.f. the __module__ and __name__ of a python class) to record the context in which the object is built. The interpreter may also wish to include some keyword arguments; e.g. recording the builder the interpreter invoked (as opposed to those to which that one delegated) as __builder__. Individual builders may also specify some keyword-only parameters (typically without __ at start and end) to which they give special meaning, such as the optional extra data one can supply for the wrapper of wrapped(). These enable builders to customise the behaviour of builders to which they delegate. Keyword-only parameters appear in a parameter list between the *-item and the **-item. It is reasonable to a expect suite wrapper to borrow from its built object and carry enriched functionality for modifying the latter, which will not retain this once initialised: e.g. new, wrap = within(...) gives wrap an attribute new lacks, __lookups__; modifying this changes what lookups new will use, for example: within new: __lookups__.append(lambda k, m=meth, o=new: getattr(m,k)(o,k)) would cause attribute-lookup on new to include a step during which it looks for the matching attribute on meth and reads this as a mapping which, given new and the attribute name as parameters, yields the appropriate value for that attribute of new (see lazy ...). One builder extending another may well do so by calling the other (using apply() or a direct python function call), rather than by exercising it in a build statement, so as to obtain both the object and its wrapper (e.g because it wants its own wrapper to provide access, at least in its own suite, to functionality present in the other builder's suite but not thereafter). This, of course, gives it the opportunity to vary the bases-tuple and add (or filter out) keyword arguments such as __bases__ (which shouldn't be supplied by the interpreter) and special (keyword-only) parameters supported by the subordinate. Suite-wrappers should usually be transient entities (unless their builder explicitly says otherwise, in which case the wrapper shouldn't carry references to genuinely transient suite-wrappers - c.f. python.instance, whose wrapper survives for use as the self argument of methods) carrying only the machinery their builder provides for the suite but not for the built object. Consequently, code which python-calls a builder, as opposed to using it in a build statement, will typically transcribe the suite wrapper's magic attributes, then discard it (the transcript will typically be stored on a wrapper being built by the given code, extending the magic provided by the original builder). Gotcha warning: make sure a suite wrapper has a __del__ attribute (callable needing no arguments) so that it can be garbage-collected without exercising the __del__ of the thing it is wrapping, which might have `undesirable' side-effects ! """ return None, None def public(name): """Which names in a namespace are public ones ? Takes one argument, a string: if an object has an attribute of that name, the latter should appear in the former's __dir__ list . """ return name[:1] != '_' # != name[-1:] __magic__ = "__look_up Today's magic key, computed when this module gets loaded." # The use of this magic key is an `as if above the bonnet' idiom which might, in # practice, by transparently unnecessary under the bonnet. It implements # aslookup()'s unpacking of the type returned by pnamespace() def aslookup(object, *, **, magic=__magic__): """Unpacks the attribute lookup of an object. Specified by: aslookup(obj)(key) is getattr(obj, key). In so far as obj is a result of a call to within, however, this is optimised so that aslookup(obj) is the lookup passed to pnamespace to produce obj. Slogan: aslookup(pnamespace(lookup)) is lookup. Note that pnamespace() is presumed to be creating an object of a new type, which subsumes the existing types that support attribute lookup, packaging the callable passed to pnamespace(). The under-the-bonnet truths of this type consist of mapping attribute lookup to calls of the function packaged. The magic attribute used in the present sketch sets out to model that under-the-bonnet reality. """ try: return getattr(object, magic) except AttributeError: return lambda k, *, **, o=object: getattr(o, k) def within(*bases, **what, magic=__magic__, spawn=pnamespace, duldel=lambda : None): """Primitive builder. Usual arguments and return for a builder: see mason(), above. The built object (first in the pair returned) borrows from the given bases; its suite wrapper (second) borrows from it and supports one extra attribute, __lookups__, which is the list of lookups tried, in turn, by the built object when it is asked for an attribute. (Well, OK, the suite wrapper also supports its safety __del__ and the magic attribute exploited by aslookup.) Thus, during the suite of a build statement using within, one can add to the lookup chain used by the built object: but this chain is `frozen' once the build statement has completed. If the suite doesn't add anything providing attribute modification functionality to __lookups__ but does attempt to bind names (i.e. modify attributes in its local namespace), it can only succeed by borrowing this functionality from a base; which will mean that it acts on the base, rather than directly on the built object. Thus a build statement using within directly, without frobbing __lookups__, provides a simple way of executing a suite of code (effectively) in the namespace of any object (use it as first base) that supports attribute modification. Hence the name I have given it - the code: def next(self): within temp (self): # this is executed `within' self: count = count + 1 try: item = seq[count] except IndexError: del item done = 1 is equivalent to: def next(self): self.count = self.count + 1 try: self.item = self.seq[self.count] except IndexError: del self.item self.done = 1 but saves all those repetitions of self. As a further example, if victim supports attribute modification: within temp (victim, helper): # This code gets executed in victim's namespace. this = that count = 1 + count reads that and count from victim or helper and stores this and count on victim. The name temp gets bound to an object borrowing from the given bases, but will typically be discarded. This sketch of the implementation presumes a primitive, pnamespace, which takes a lookup and returns a python object whose presumed type is defined to support only attribute lookup (and any functionality supported by magic attributes obtained thereby), which it does by calling the lookup that was passed to pnamespace() when it created the object. See aslookup(), above. Alternatively, within could totally supersede pnamespace() as the primitive (under the bonnet). """ try: what['__builder__'] except KeyError: what['__builder__'] = within # First, build the built object's lookup: row = map(aslookup, bases) # will become __lookups__ attribute of wrapper def getat(key, *, **, chain=row, data=what): if key == '__dir__': bok = {} for nom in data.keys(): bok[nom] = None for link in chain: try: more = link(key) except AttributeError: pass else: for nom in more: bok[nom] = None return filter(public, bok.keys()) try: return data[key] except KeyError: pass for link in chain: try: return link(key) except AttributeError: pass raise AttributeError, key # Store magic attribute: what[magic] = getat # Now build the object's suite-wrapper: bok = {} def getit(key, *, **, bok=bok, fetch=getat): # no contribution to __dir__; just fall back on getat try: return bok[key] except KeyError: pass # Finally, fall back on borrowing from res. return fetch(key) # fill bok with suitable data: bok.update({ magic: getit, '__lookups__': row, '__del__': duldel }) # (Well, we could have __del__ clear bok, as long as it put duldel in # as __del__ thereafter. But I see no point to doing so.) # Finally, return the pair: return spawn(getat), spawn(getit) del __magic__, pnamespace fauuoq = """Everything above could be hidden under the bonnet of the python engine: the contents of python.py merely need to use within as a builder and insert things into the __lookups__ attribute it provides. Implementing within and aslookup under the bonnet would make it possible to do away with pnamespace and the magic attribute used in the above.""" # hint: half turn fauuoq - what's above it is under the bonnet. _rcs_log = """ $Log: bonnet.py,v $ Revision 1.6 2005/02/12 17:39:46 eddy Give object a __builder__ attribute. Another renaming to avoid dict. Revision 1.5 2005/02/12 16:47:20 eddy Doc fixes and a renaming, to avoid the name dict. Revision 1.4 2002/01/30 17:52:21 eddy Major revolution. Inlined post-within precursors in instance and simplified the resulting mess; added __wrap__ to carry wrapper methods from class; shunted class-specific magic (redirected attribute modification) into class, unburdening (gennie renamed as) builder. Moved all surviving precursors of instance from python.py to bonnet.py, emptying old junk from bonnet.py into novelty.py. Made the function defining a scheme receive the lookups list as third parameter; removed access to this list from instance wrappers; made wrapper's __dict__-based attribute modifier fallbacks come early, like __wrap__-derived kit, rather than late. Changed __meth__ to __method__, `generate' to `build', __schemes__ to __magic__. Re-documented plenty of stuff, revised web pages. Revision 1.3 2002/01/23 04:02:34 eddy made __dir__ attribute universal; renamed initspace to extend; cleaned up scheme inheritance Revision 1.2 2002/01/22 18:52:24 eddy Removed redundant import of no-longer extant module class.py Revision 1.1 2001/10/17 18:00:19 eddy added in all files previously missing from RCS Revision 1.4 2000/01/15 18:28:43 eddy instspace has vanished: use initspace instead. Revision 1.3 2000/01/15 18:10:15 eddy Reduced some more. Revision 1.2 2000/01/02 18:39:45 eddy Much reduced. Initial Revision 1.1 1999/12/23 02:25:20 eddy """