Guido did such a good job of the original design of python that I think I can see a way to fit in a layer `before' classes, which are a primitive variety of object. It is then possible (given my liberalisation of the def statement's parameter sequence to allow defaulted parameters after special ones) to use these to implement the existing class structure, leaving elbow-room to tinker with details in all the meta-ways we like without having to change what the python engine does.
The package hierarchy solves the practicalities of letting us have modules with modules inside them, loading potentially huge sub-modules within those only when needed. At the opposite extreme, there are modules which want to organise some data into a convenient namespace, which naturally wants to be hierarchical. For example, consider a module of constants, which might want a namespace like (in some imaginary `namespace description language'):
const: # a module
math: # a namespace (sub-module)
pi # a datum, const.math.pi
Napier # another
vacuum: # another sub-module
c, G, h # more data
Planck: # a sub-module
mass, length, time, momentum
# ...
After `from const import *' I can refer to math.pi, Planck.mass and so on: likewise Earth.body.mass and Earth.orbit.centre.body.radius if defined somewhere in the `...' part. I could achieve that by having actual modules math, vacuum, Planck, but it'd be painfully heavy-weight. I could achieve it by defining a vacuous class and making instances of it for math, vacuum, etc., but that uses the class mechanism, so it can't be imported from. What that vacuous class (class namespace: pass) is providing is simply a raw object.
It seems reasonable to suppose that one could (back when the class mechanism was added) have added a statement of form
object_stmt: "object" [ NS-spec "." ] identifier [ tuple ] ":" suite
which, when executed,
This lets us create the above sub-module hierarchy using (as the text of const.py, with a few other data thrown in)
import math
_math = math
del math
object math:
pi, Napier = _math.pi, _math.exp(1)
object vacuum:
c, Z0, G, h = .3e9, 376.73, 66e-12, .66e-33
permittivity, permeability = 1 / Z0 / c, Z0 / c
object Planck:
speed, action, impedance = vacuum.c, vacuum.h, vacuum.Z0
__Newton = vacuum.G / pow(speed, 3)
# season the above to taste with factors of pi and 2.
charge, momentum, length = map(_math.sqrt, (action / impedance,
action / __Newton,
action * __Newton))
mass, energy = momentum / speed, momentum * speed
time, current = length / speed, charge * speed
del _math
object Sun:
object body: mass, radius = 2e30, .4e9
object Earth:
object body: mass, radius = 6e24, 6.4e6
object orbit:
centre = Sun
radius = .15e12
period = 31.6e6
So all very well, we can package data in namespaces. If we can do that, of course, we can put functions into namespaces and, potentially, call them methods. If all namespaces are to be equal, and all kinds of data treated on a common footing, the `standard interface' methods of an object, such as __add__, __call__ and __str__, need to be callables which can do their job without needing to be told about the object whose method they are: normally, by being `bound methods' to which the relevant self argument `has already been passed'. What matters is that one invokes this.__add__(that) to implement this + that. I insist on this for both instances and classes (as the alternative it to have a plethora of families of magic names, one for instances, another for classes and so on, all implementing the same things). This requires a shift to using a sub-namespace of a class, say .__method__, from which instances of the class do `inherit-with-currie()ing'.
This, as I outline while discussing attribute lookup, means we have to use the suite of a class statement as the initialisation code of a sub-object of the class object, rather than as that of the object itself. This complicates the simple notion introduced above: the class and object statements have subtly different semantics. Not to worry - we can build them. First, amend the object statement:
object_stmt: callable [ NS-spec "." ] identifier [ tuple ] ":" suite
in which the callable takes, as its arguments, the [ tuple ], defaulting to an empty tuple (no arguments): it returns a pair (i.e. a 2-tuple or `twople') of objects. The object statement calls this, binds the first object to the [ NS-spec "." ] identifier and then executes the suite in the namespace of the second. Suppose we have a primitive accessible to the following code under the name pnamespace (in whose pronunciation the first p is silent) which takes a lookup (a callable expecting one input, the name, and returning a value, the attribute), returning the result of packaging this as a namespace. Unreserve class and object and add at least the former to builtins using definitions along these lines:
def object(*bases):
chain = []
def getat(key, __c=chain):
for link in __c:
try: return link(key)
except AttributeError: pass
raise AttributeError, key
def borrow(key, __b=list(bases)):
if key == '__bases__': return __b
for b in __b:
try: return getattr(b, key)
except AttributeError: pass
raise AttributeError, key
chain.append(borrow)
dict = { '__lookups__': chain }
def setit(key, val, __d=dict): __d[key] = val
def delit(key, __d=dict):
try: del __d[key]
except KeyError: raise AttributeError, key
dict.update({ '__setattr__': setit,
'__delattr__': delit })
def getit(key, __d=dict):
if key == '__dict__': return __d
try: return __d[key]
except KeyError: raise AttributeError
chain.append(getit)
res = pnamespace(getat)
return res, res
Note that __getattr__ is merely a protocol that an object may have defined for it (by, effectively, inserting it in __lookups__), whereas __setattr__ and __delattr__ have to be taken as actual protocols of python (along with, e.g., addition, string, ...). That's object done. Now for
def class(*bases):
object new bases:
# new is a class ...
def __call__(*args, **what, klaz=new):
# ... which is instanciated by calling it
object inst (klaz): pass # so we'll be returning inst
def enmeth(func, __f=inst):
# that is, bind-to-inst, turning a function into a method of inst
def meth(*args, **what, c=func, i=__f):
return apply(c, (i,) + args, what)
return meth
def inherit(key, __c=klaz, __m=enmeth):
# the part of inst's namespace lookup which produces methods
if key == '__class__': return __c
return __m(getattr(__c.__method__, key))
inst.__lookups__[-1:-1] = [ inherit ] # just before borrow
def getat(key, __o=inst):
# the __getattr__ protocol
if key != '__getattr__':
try: return __o.__getattr__(key)
except AttributeError: pass
raise AttributeError, (__o, key)
# put that last of all in our lookups:
inst.__lookups__.append(getat)
# Finally, perform any __init__ method inst now supports:
try: init = inst.__init__
except AttributeError:
if args or what:
raise TypeError, ('no __init__ but args given to instantiation',
klaz, args, what)
else: apply(init, args, what)
return inst
row = [] # row of __method__ attributes of bases.
for b in bases:
try: base = b.__method__
except AttributeError: pass
else: row.append(base)
object new.__method__ tuple(row): pass
# new.__bases__.append(new.__method__) # optional
return new, new.__method__
Note that, whereas object's two returns were the new object, those of class aren't the same: and, strictly, this will put things I wanted as non-methods into the __method__ object's namespace, though they really belong in that of the new class itself (and, but for this, I'd leave off having the class borrow from its __method__ attribute). That can be bypassed by putting these `attributes' of a class onto a plain object (which has no __method__ attribute) and using this as a base for the new class (it gets ignored when building the bases-tuple for __method__):
class FileSystem:
def join(self, *args):
return string.joinfields(args, self.separator)
object _posix:
separator = '/'
class UnixFS(_posix, FileSystem):
def mountpoint(self):
# whatever
Note that setting UnixFS's attributes subsequently won't change the value lurking in _posix: subsequently deleting if from UnixFS will then restore access to _posix's value for it. This could be viewed as a Good Thing ;^)
Where the object statement calls for a tuple, of bases, the above needs it to accept an expression yielding a tuple: this is necessary if the definition of class above is to be able to delegate to object. [Otherwise, we could consider taking the tuple as an arbitrary argument-pack, as in a function call, allowing it to contain name=value entries:
def object(*bases, **dict):
# ...
would seem to make some sense - though it must involve replacing the initialisation of dict in the above; move the __lookups__ assignment into the later dict.update()'s argument. This could provide an alternative way for a class to acquire its non-method attributes, though it may be necessary to frob the argument lists above (remove the * from *bases, I suspect, and perturb the reading of the bases list). I'm not persuaded that this is a good idea, but potentially this could provide a way to fine-tune the definition of a namespace-builder other than object.]
Now,
def within(obj): return lambda __o=obj, *igno, **red: None, __o
and we can execute some code in the namespace of an existing object:
within dummy (obj):
at = tribute
def meth(*args, **what, self=obj):
# ...
# ...
which incidentally binds dummy to None but, more usefully, lets us execute an arbitrary piece of code `in' obj's namespace, adding and deleting attributes, defining methods and whatever - as if in a continuation of the suite which created obj. Likewise, one can pass within(obj) to some function whose job is to perform some standard operation on a namespace. Now, within(obj) ignored any bases I could have given after dummy: what could I have done with them ? One option looks like this:
def within(obj):
def result(__o=obj, *bases):
object tool (__o,) + bases:
del __setattr__, __delattr__
return None, tool
return result
Now, tool is going to borrow obj's __setattr__ and __delattr__, which (of course) operate on obj's namespace: so code executed in tool's namespace reads from the namespaces of obj and whatever `bases' we put after dummy and writes to obj's namespace. Note that the base engine never needs to support more than the beautiful sufficiency of Guido's local/global/builtin chain: in particular, it doesn't need to get tangled up in deciding which more complex system to use - since it provides the base on which to build arbitrary namespace manipulation quite transparently.
I based the simple object statement on the notion of `sub-namespace of a module'. Consequently, I can trivially model the process of importing a module as if import used the contents of a file as the suite of an object statement whose builder differs from the object defined above only in some minor details - it sets a few module-specific attributes and records the new object in the importer's private namespace, sys.modules. In like kind, the builder for package objects, while it has a rather more sophisticated task to perform (just as class is more sophisticated than object, above), is only different from the builders above in detail, not in fundamental kind. Indeed, like ... (umm, I seem to have broken in mid-sentence)
Implicitly, the constructors used by the importer to create package and module namespaces can be modelled as though import were an object statement - indeed, a module is then almost exactly an object (though it also gets things like __name__ set).
Written by Eddy.