"""Detailed implementation - see python.html
Where, in python.html and related texts, I used code fragments to suggest ways
of doing parts of the job described, I tried to ensure that they were
intelligible in terms of what I'd (thus far) said in the relevant text. The
following code presumes all the ideas on which I eventually settled in the
discussion: here I implement the relevant stuff as a cohesive whole. This
started out simply as a transcript of those fragments, suitably combined, and
has evolved (extensively).
A `lookup' is a function taking one (string) argument. The argument is
understood as an attribute name; what the lookup returns will be understood as
the value of that attribute; if it doesn't provide a value for the given
attribute, it signals this by raising AttributeError. While the primitive
pnamespace(), see bonnet.py, takes a lookup and packages it as an `object', most
lookups will appear in lists (lookup chains). See bonnet.py's mason() and
within().
One special key is to be supported by all lookups: the __dir__ attribute should
be a list of all attribute names that don't start or end in an underscore.
In bonnet.py, along with various tools belonging `under the bonnet' of an
interpreter, you'll find documentation of what a `builder' is, for use in place
of `class' in the build statement, generalising python's class statement.
Contents:
instance -- basic builder implementing the magic of instances.
architect -- archetype (i.e. typedef documentation) for builder classes.
method -- an object specifying the magic for class-style method inheritance.
builder -- a builder class which builds builder classes.
class -- a builder which builds (very nearly) a python class.
See check.py for a sketch of how to type-check namespaces; novelty.py for
various bits and pieces; lazy.py for how to extend classes with further lookup
schemes.
The lookup list for a bonnet.within has one entry per base; the other builders
extend this in various ways, principally via the magic mechanisms of instance,
which see. Greater sophistication emerges once we get to builder and class.
$Id: python.py,v 1.8 2005/02/12 17:46:26 eddy Exp $
""" # "
from bonnet import within, public, aslookup
# Scheme names it would be unwise to use:
pythonic_protocols = ( 'lookups', 'self', 'data', 'wrap', 'magic', 'dir', # mine
# attributes:
'bases', 'class', 'dict',
# standard python methods:
'init', 'len', 'del', 'nonzero', 'cmp', 'rcmp',
'getattr', 'setattr', 'delattr',
'getitem', 'setitem', 'delitem',
'getslice', 'setslice', 'delslice',
'neg', 'add', 'radd', 'sub', 'rsub', 'mul', 'rmul',
'div', 'rdiv', 'mod', 'rmod', 'divmod', 'rdivmod' )
def instance(*bases, class=None, **what, exclude=pythonic_protocols):
"""Builds an instance.
Is a builder: see documentation of bonnet.mason for documentation of its
parameters. Recognises one keyword argument, class, with default None.
If class is not None, it becomes the instance's __class__ attribute.
The suite wrapper has, as its .__self__ attribute, the built object.
Neither object can see a __lookups__ attribute: this is deliberate !
Any .__wrap__ attribute of class is used to specify methods for the suite
wrapper; the wrapper accesses these before falling back on the built object.
In the absence of such an attribute, __dict__-based attribute modification
functionality is provided to the wrapper. Note that this is the case when
class is None, in particular when a build statement uses instance as its
builder. If you do not want such functionality, equip your class with a
.__wrap__ attribute which does *not* provide it. If either __wrap__ or the
fall-back supplies attribute modifiers, using these to store __setattr__ or
__delattr__ will only affect the built instance, not the suite wrapper: it
will carry on using the same attribute modifiers it was created with.
Any .__magic__ attribute of class is used to specify magic lookup schemes
for the built object. Each public attribute of class.__magic__ should be a
callable taking three arguments; the name of the attribute, with __ added
before and after, is used as an attribute name of class; if class has such
an attribute, the callable is called, during instantiation, with this as
first argument, the instance's suite-wrapper as second and the instance's
lookup list as third. The callable will typically use the magic attribute
and suite wrapper to build a lookup which it will add to the start or end of
the lookup chain. Note that the various magic schemes are implemented in
haphazard order.
Finally, the built object is given a dictionary-based lookup, consulting the
dictionary that the suite wrapper sees as .__dict__, and a lookup
implementing the __getattr__ protocol; the former is its first lookup, the
latter is its last.
Magic functionality has the option of keeping the suite wrapper around after
the suite has executed, e.g. as the self parameter of methods. In such a
case, if you want to keep the outside world's hands off the wrapper, your
magic needs to be careful not to go handing it out to strangers. If a
method needs to return the built object, for instance, it should return
self.__self__ rather than self (which is the wrapper).
See method, below, for implementation of some schemes; the various builders
in lazy.py for similar; and builder, below, for how values for class get to
provide sensible magic. """
try: what['__builder__']
except KeyError: what['__builder__'] = instance
if class is not None: what['__class__'] = class
inst, shell = apply(within, bases, what)
# now build the suite wrapper:
wrap, husk = within(inst, __self__=inst, __dict__={}, __del__=lambda : None)
# equip suite wrapper with primitive behaviours:
try: sweet = class.__wrap__
except AttributeError:
# provide fall-backs:
def setit(key, val, *, **, bok=wrap.__dict__): bok[key] = val
def delit(key, *, **, bok=wrap.__dict__): del bok[key]
def grab(key, *, **,
bok={ '__setattr__': setit, '__delattr__': delit }):
try: return bok[key]
except KeyError: raise AttributeError, key
else:
# use whatever sweet gives us (and no more):
def grab(key, *, **, glean=aslookup(sweet), obj=wrap):
def ans(*args, **what, meth=glean(key), self=obj):
return apply(meth, (self,) + args, what)
return ans
husk.__lookups__.insert(0, grab)
del husk
# implement all schemes for the instance itself:
try: schemes = class.__magic__
except AttributeError: pass
else:
for nom in schemes.__dir__:
if nom not in exclude:
try: obj = getattr(class, '__' + nom + '__')
except AttributeError: pass
else: getattr(schemes, nom)(obj, wrap, shell.__lookups__):
# finally, __getattr__ and __dict__ for attribute lookup; but always first
# and last, respectively, not in hap-hazard order among schemes.
def getit(key, *, **, book=wrap.__dict__):
if key == '__dir__': return filter(public, book.keys())
try: return book[key]
except KeyError: raise AttributeError, key
def getat(key, *, **, obj=inst):
if key != '__getattr__':
try: return obj.__getattr__(key)
except AttributeError: pass
raise AttributeError, key
shell.__lookups__.insert(0, getit)
shell.__lookups__.append(getat)
# outer = within(wrap, __lookups__=shell.__lookups__, __del__=lambda : None)[0]
del shell # fatuous: but stated for emphasis ...
# Could use outer in place of wrap to support __lookups__ in the suite:
return inst, wrap
# typedef ...
instance architect:
# Note that instance supports attribute modification in its suite, which the
# interpreter could use to store the __doc__ - but it should provide __doc__
# as a keyword argument to builders (e.g. so that a within can begin with a
# doc comment explaining what its suite is doing, without messing up the doc
# of a base).
"""The generic interface for builder classes.
A builder class is archetypically instantiated by using it in a build
statement: it builds an instance, equipping it with magic. It is, equally
callable, with the usual arguments and returns of a builder. See
bonnet.mason(), the archetype of builders, for further details.
A builder class provides its built instances with attribute lookup
functionality, as documented by instance, above. This will include
borrowing attributes from the __data__ attribute of the builder class, if it
has one, else from the builder class itself; more importantly, it will
involve some lookups provided by the __magic__ attribute of the builder
class.
In pursuit of that, the __magic__ attribute of a builder class must borrow
from the __magic__ attributes of *all* bases of the builder class that have
one. Likewise for each magic attribute implied by an attribute of
__magic__, e.g. if __magic__ supports method, the __method__ of the derived
builder class must have those of the base builder classes as its bases. In
both case, though: if no base has the magic attribute, the derived class
doesn't need one; if only one base has it, this can simply be borrowed
without creating a stub via which to access it (and we can uniquify the list
of bases before testing this ;^)
The same base-forwarding is applied to the __wrap__ attribute, which equips
the suite-wrapper with interesting behaviour: if absent, instance gives its
suite wrapper fall-back attribute modification behaviour; but if __wrap__ is
present, attribute modification will only be feasible in so far as __wrap__
provides for it (i.e. you'll usually provide __setattr__ and __delattr__ on
any __wrap__, unless you *intend* to forbid relevant functionality even in
the suite).
Similar base-forwarding is applied to the __data__ attribute, save that a
base which lacks this attribute is used in its stead, and a __data__
attribute is always provided on the builder class, even if it has no bases
(and, consequently, no attributes
In the suite of a builder class, use instance to build a replacement for any
magic object:
instance __method__ (__method__,):
def stuff(self, src, wrap): pass # or whatever
This will replace the __method__ attribute of the builder class with one
borrowing from the one provided when the builder class was built and
extending it with the attributes added during the suite (here: stuff).
A magic lookup scheme is at liberty to provide the built instance with
access to __lookups__ (and __dict__), or to equip it with sophisticated
attribute modification functionality.
For illustrations, see lazy.py and check.py """
instance __magic__:
"""__magic__ provides hooks for specific lookup schemes
A (class or) builder class has a __magic__ attribute: which borrows
from the matching attributes of any bases (except when exactly one base
has this attribute, in which case the __magic__ attribute is simply
borrowed whole).
Each public attribute of __magic__ must be callable: it describes a
`scheme' by which the builder class equips its instances with useful
behaviour. The scheme comprises two parts: the callable, which sets up
each instance to have the relevant behaviour, and a magic attribute of
the builder class. The latter's name is obtained by wrapping that of
the scheme in __...__, e.g. the scheme named method is associated with
the magic __method__ attribute of (classes and) builder classes.
The public attributes of __magic__ must be callable; when an instance
of the builder class is initialised, this callable is invoked with
arguments
* the associated magic attribute of the builder class,
* the wrapper of the instance being initialised and
* the lookup-list of the instance being built.
The callables for various schemes are invoked in haphazard order.
Before any of them are called, the instance's __lookups__ list only
contains the lookups of bases from which the instance borrows. After
all schemes have been called, the instance's dictionary-based lookup is
installed as first lookup and its __getattr__ lookup is installed as
last lookup. (Ensuring these two are first and last prevents us from
being able to implement them via __magic__ themselves.)
Each scheme's callable will typically install a lookup in the instance's
__lookups__ list: a lookup scheme should .insert(0, ...) to be consulted
before bases or .append(...) to be consulted after them. """
pass
def __call__(*bases, **what): # a builder class always has a __call__
"""The call attribute of a builder class is a builder.
It is invoked with the usual arguments for a builder, bases and keywords,
and its return is the usual (built-object, suite-wrapper) pair.
This is the signature of the builder class' __call__ attribute: note,
however, that the attribute may actually be provided for by inheritance
from whatever built the builder class, so the underlying function
might have a different signature: see, for instance, the __call__
provided by builder, which both it and class inherit. """
# Superficial stub:
return apply(instance, bases, what)
# That should make it easier to keep track of how builder is constructed.
# OK, so let's specify a few schemes:
# first, a pseudo-scheme:
instance wrap:
instance __magic__:
def wrap(funcs, inst, lookups): pass # stub to force propagation of __wrap__
instance __wrap__:
# rely on instance to supply a suitable __del__ ...
def __delattr__(self, key): del self.__dict__[key]
def __setattr__(self, key, value): self.__dict__[key] = value
# then the non-method attribute carrier:
instance data:
instance __magic__:
def data(given, inst, lookups):
# Should an instance borrow from its class before or after its bases ?
lookups.insert(0, aslookup(given))
__data__ = None
# and the method carrier:
instance method:
instance __magic__:
def method(funcs, inst, lookups):
def fetch(key, *, **, meth=aslookup(funcs), obj=inst):
if key == '__dir__': return meth(key)
def ans(*args, **what, func=meth(key), self=obj):
return apply(func, (self,) + args, what)
return ans
# Should an instance inherit from its class before or after borrowing from its bases ?
lookups.insert(0, fetch)
instance __method__:
# and we may as well have the default forms of a few read-only methods ...
def __init__(self):
"""Default instance initialiser."""
pass
def __del__(self=None, *, **, naked=__self__.__del__):
"""Default instance destructor.
Note that any __del__ defined on a __method__ or __wrap__ object
(or, indeed, any similar curriable-carrier) must give its parameter
a default: the function *will* be called when its carrier is tidied
away (just as methods curried from it are called when relevant
instances are tidied away) with no arguments; it should be sure to
do the right thing in that instance ! Thus the typical definition
of a __del__ method will resemble:
def __del__(self=None, *, **, up=__self__.__del__):
if self is None: up()
else:
# do whatever is apt for my instance, self; ending in
up(self) # unless up is a raw __del__
wherein a `raw' __del__ is the one stored on the carrier (which is
the __self__ of the wrapper in whose namespace the __del__ gets
defined) to tidy *it* away. If you don't know whether up is raw,
protect the second call to up in a try clause catching TypeError.
"""
if self is None: naked()
else: pass # do stuff appropriate to self.
def __repr__(self):
ans = self.__text(repr)
# no point computing that again later ...
self.__repr__ = lambda a=ans: a
return ans
def __str__(self):
ans = self.__text(str)
# no point computing that again later ...
self.__str__ = lambda a=ans: a
return ans
def __text(self, text):
form = 'object'
try: k = self.__class__
except AttributeError: pass
else:
if self is not k:
try: nom = k.__name__
except AttributeError: nom = text(k)
else:
try: nom = '%s.%s' % (k.__module__, nom)
except AttributeError: pass
form = nom + ' instance'
return '<' + form + ' at ' + hex(id(self)) + '>'
instance builder (method, data, wrap):
"""Primitive builder for sophisticated objects.
"""
# supply call method:
instance __method__ (__method__,):
def __attrseq(name, seq): # tool function
# uniquified map(lambda x: getattr(x, name), seq) with None thrown out, if present.
row = [ None ] # so we know where it is and uniquifying ensures it's not duplicated
for b in seq:
try: here = getattr(b, name)
except AttributeError: pass
else:
# if here not in row: row.append(here) # in uses == rather than is as test
for it in row:
if it is here: break
else: row.append(here)
return row[1:] # omit None
def __call__(self, *bases, **what,
forbid=pythonic_protocols,
glean=__attrseq, spawn=instance):
# include self's schemes as well as those of bases:
sources = bases + (self,)
row = glean('__magic__', sources)
if len(row) > 1:
names = what['__magic__'] = apply(within, tuple(row))[0]
names = names.__dir__
elif row: names = row[0].__dir__
else: names = ()
for nom in names:
# arguably: if nom in forbid: raise NameError, ...
assert nom not in forbid, 'scheme name and deep magic clash: ' + nom
name = '__' + nom + '__'
row = glean(name, sources)
if len(row) > 1: what[name] = apply(within, tuple(row))[0]
try: what['__builder__']
except KeyError: what['__builder__'] = self
what.update({ '__bases__': bases, 'class': self })
return apply(spawn, bases, what)
del __attrseq
# Arrange to be usable as a builder class:
# we have to list these bases; but what we build won't have to.
instance __magic__ (method.__magic__, data.__magic__, wrap.__magic__): pass
# Bury a mimic of method's currier in the __getattr__ hook:
def __getattr__(key, *, **, meths=aslookup(__method__)):
def thod(*args, **what, func=meths(key)):
return apply(func, (builder,) + args, what)
return thod
# builder is now callable and behaves a lot like one of its own instances;^)
# Consider:
# builder builder (builder,): pass # change what builder refers to, but not its behaviour.
# or, likewise,
# builder builder (method, data, wrap): ... a suite containing just the __method__ above ...
del pythonic_protocols
builder class (method, data, wrap):
"""A builder class which builds (conventional) classes.
Arguments and returns are as usual for builders.
The built objects are classes.
As discussed in python2.html, this differs from a python 1 class is that
referring to methods of the class explicitly (as opposed to via inheritance)
requires dereference via .__method__: classname.__method__.methodname """
instance __wrap__ (__wrap__,):
def __get(self, magic):
"""Provides for copy-on-write, as needed for attribute modification."""
try: return self.__dict__[magic]
except KeyError: pass
try: ans = instance(getattr(self, magic))
except AttributeError: ans = instance()
setattr(self, magic, ans)
return ans
def __delattr__(self, key, *, **):
missing = 1
for it in ( '__data__', '__method__' ):
try: delattr(self.__get(it), key)
except AttributeError: pass
else: missing = None
# but I won't let you del the magic attributes of schemes !
if missing: raise AttributeError, key
def __setattr__(self, key, val, *, **):
if (key[:2] == '__' == key[-2:] and hasattr(self.__magic__, key[2:-2])):
self.__dict__[key] = val
elif callable(val):
setattr(self.__get('__method__'), key, val)
else:
setattr(self.__get('__data__'), key, val)
instance __method__ (__method__,):
# Classes use this __call__ where builder classes, such as class, use
# the one they inherit from builder.
def __call__(self, *args, **what, spawn=instance):
"""Return a new instance of class self.
The instance is built, thereby obtaining all the lookups of the
class' various schemes; using those lookups, an __init__ method is
sought on the instance; this is invoked with the arguments passed to
the present __call__. """
try: what['__builder__']
except KeyError: what['__builder__'] = class
inst, wrap = spawn(class=self)
# Perform any __init__ method wrap supports:
apply(wrap.__init__, args, what)
# Return the wrapper: python instances are the same things as appear
# as the self of their methods.
return wrap
_rcs_log = """
$Log: python.py,v $
Revision 1.8 2005/02/12 17:46:26 eddy
Add support for __builder__ attribute (in case not supplied by parser ...)
Revision 1.7 2005/02/12 16:45:33 eddy
Lots of doc fixes, some tidy-up, more systematic handling of imprudent
schemes, borrow class data early.
Revision 1.6 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.5 2002/01/26 15:11:40 eddy
tidy-up and simplification; moved instance early; next stop wyrm.py ...
Revision 1.4 2002/01/23 04:02:34 eddy
made __dir__ attribute universal; renamed initspace to extend; cleaned up scheme inheritance
Revision 1.3 2002/01/22 19:16:24 eddy
resync.d justthecode with python; tweaked some docs; untabified
Revision 1.2 2002/01/22 18:58:13 eddy
changed generators round to use __schemes__, with meth as just one such; added lazy.py
Revision 1.1 2001/10/17 18:00:20 eddy
added in all files previously missing from RCS
Revision 1.15 2000/01/16 23:17:19 eddy
Restored the missing what.update() in gennie (doh).
Provided gennie and initspace with more configurability (for check.py).
Revision 1.14 2000/01/15 18:08:47 eddy
Re-built everyone using wrapped().
Revision 1.13 2000/01/14 19:26:36 eddy
Major progress ... now for another revolution.
Revision 1.12 2000/01/08 13:43:24 eddy
Built instspace primitively, obsoleting fixspace (and namespace) and
making it practical to generate class here, obsoleting class.py
Revision 1.11 2000/01/07 23:19:00 eddy
A revolution is a-coming ...
Tidied up fixspace (plus some tweaks) just in time to obsolete it ...
Revision 1.10 2000/01/03 23:14:11 eddy
class.py no longer needs namespace: fixspace, within and aslookup suffice ;^)
Revision 1.9 2000/01/02 18:37:10 eddy
Major over-haul of docs. Added generator() for generic doc.
The closure revolution. Removal of notes and of type-check sketch.
Revision 1.8 1999/12/18 17:39:18 eddy
Tweaks.
Revision 1.7 1999/12/18 15:48:07 eddy
Major revolution. Made __lookups__ private to generator-suite.
Re-arranged everything else to suit. Added draft type-checker.
Revision 1.6 1999/12/13 20:22:53 eddy
Removed redundant caveat, added missing bits to doc, tweaked stuff.
Revision 1.5 1999/12/13 19:47:17 eddy
Like I said in April, revolution has happened. Removed class and
ancillaries to class.py, rearranged the remains.
Revision 1.4 1999/04/07 21:32:03 eddy
The newdict revolution blew over, re-emerged, got co-opted and set the
ground for a roll up to above a dotted line, which has now emerged, now
calling for a further revolution.
Revision 1.3 1999/04/06 22:07:54 eddy
Time to refactor newdict
Revision 1.2 1999/03/28 21:15:21 eddy
Some steady work.
Initial Revision 1.1 1999/03/27 01:29:14 eddy
"""