[Types-sig] method binding

Edward Welbourne Edward Welbourne <eddyw@lsl.co.uk>
Tue, 8 Dec 1998 12:21:19 GMT

All values are objects, including the objects (classes) which describe
the behaviours of other objects (their instances).  One object can `use'
another as `its class' and it can use others in many other ways.

We have three distinct kinds of deferral for attribute lookup at present:
the object (having failed to find the attribute in its own namespace)

 i) asks its bases if any of them has the attribute: if so, it treats
    this just as if it'd found it in its own namespace.  At present,
    only classes do this, but all objects should be entitled to use
    .__bases__ for implicit deferral in the same way.

 ii) asks its class to give it such an attribute: the class looks up the
    attribute on itself and offers that for the job, possibly binding
    this answer as a method bound to the object which asked.

 iii) looks for a __getattr__ attribute of its own and calls that.  This
    can do smarter things than just `if it makes sense to bind the
    result I would be giving, I will'.

So, Just, I reject the unification of bases and class.

The bases of an object are just some places where the object looks for
uncompilicated values, with no `maybe binding' rule.  Things it finds
there are treated just the same way as things it finds in its own
namespace.  This is what we see at present if we ask for an attribute of
a class and get our answer from one of its bases.  So __bases__ provides
for delegation, only nobody'd commented on it.

If the object doesn't find the attribute in its own namespace or in its
bases, it asks its class/type/kind/whatever to bail it out.  At this
point we're doing something entirely different from bases: we're asking
someone else to generate a value, by whatever means, suitable for use as
the object's relevant attribute.  Both i) and iii) are like this.  This
is where `maybe binding' happens.

So I reckon our objects have got:

 a (personal) namespace, .__dict__
 a DG of bases, .__bases__
	(I'm not assuming my Directed Graph is acyclic)
 a sequence of assistors, .__classes__

and attribute lookup works like this:

def getattr(object, key):
	try: return object.__dict__[key]
	except KeyError: pass
	# if key in ( '__bases__', '__classes__', '__getattr__',
	# '__getinstattr__' ): raise AttributeError, 'perhaps'

	for base in object.__bases__:
		try: return getattr(base, key)
		except AttributeError: pass

	for helper in object.__classes__:
		try: meth = getattr(helper, '__getinstattr__')
		except AttributeError: pass
			try: return meth(object, key)
			except AttributeError: pass

	if key != '__getattr__':
		# beware the unhandled recursion !
		try: meth = getattr(object, '__getattr__')
		except AttributeError: pass
			try: return meth(object, key)
			except AttributeError: pass

	raise AttributeError, key

subject, of course, to the name lookups that the above does in the style
ob.name all being ones that some suitable magic can do without having to
call getattr.

Note that `maybe binding' activities are entirely the business of the
__getinstattr__ defined by something our object is using as a class and
of the __getattr__ of the object itself.  If you want to get a bound
method out of the __dict__ or __bases__ lookup, you have to have stored
the attribute as a bound method in the first place (and, then, it's
bound to whichever base you bound it to, if you bound it to a base).

So: there's no division into two kinds of entity, there's just a
division into two ways that one object can use others as a means of
getting hold of attributes.  In python, `a means of getting hold of
attributes' is support for the getattr interface: and support for
interfaces is entirely defined in terms of getting hold of attributes.
Which is why we're talking about attribute munging rather than any of
the other interfaces: it is the base of all interfaces.

There's also no benefit in doing fancy and different things based on
whether or not the object whose attribute we're returning has got a name
or class attribute.  These are irrelevant.  All the method binding is
contained in:

class Class:
	"""An object for use as a base by objects being used as classes.

	Defines the basic __getinstattr__ that a class provides for its
	instances. """

	def __getinstattr__(self, obj, key):
		meth = getattr(self, key)
		try: return meth.bind(obj)
		except BindingError: return meth

though, of course, whenever getattr() falls back on the object's
.__getattr__, recursing on itself, the result it got back may have been
bound to the object; and this method may, in turn, chose to bind the
result *it* will be returning likewise.

Must get on with some work now,