[Types-sig] method binding

Edward Welbourne Edward Welbourne <eddyw@lsl.co.uk>
Wed, 9 Dec 1998 17:04:27 GMT

>> No `except ...' clause.  Not `almost': exactly.
> Ah.  This is why I do these point-by-point summaries.  I seem to have
> misread the following (from your original article):
>> ...
> I took "no `maybe binding' rule" to mean that functions aren't wrapped as
> methods or bound to an instance.

and that was exactly what I meant.  The value an entry in an object's
__protos__ gives back to getattr is the value getattr'll use for the
attribute it was asking to see at the time.  Of course, the entry in the
object's __protos__ may be something which *did* bind the attribute to
the object, but that's none of getattr's business:

  getattr doesn't wrap or bind methods etc *ever* *at all*

but, of course, the __protos__ it invokes may be any manner of weird and
whacky stuff which might make vanilla icecream out of the object whose
attribute we wanted and return that as the attribute.  But getattr just
passes the result on as the attribute.

def readattr(obj, key):
	try: return obj.__dict__[key]
	except KeyError: raise AttributeError, key

def _getattr(obj, key):
	if key == '__getattr__': raise AttributeError, key
	fetch = getattr(obj, '__getattr__')
	return fetch(key)

Class.__protos__ = [
	readattr,	# so Class can look in its __dict__
	_getattr ]	# useless unles Class.__dict__.has_key('__getattr__')

getinstattr = Class.__getinstattr__.bind(Class)	# I seem to need this

First, Class has a __dict__ which contains __getinstattr__, which
readattr will find, so we get hold of this function expecting three
args: we bind it to Class to get getinstattr.

A.__protos__ = [
	readattr,	# A can read its __dict__, which has_key('fred')
	lambda o,k,g=getinstattr: g(o,k),	# A is of class Class
	_getattr ]

So A has 'fred' from its __dict__ the easy way.  If we look for
A.__getinstattr__, we'll be calling getinstattr(A, '__getinstattr__'),
which amounts to calling Class.__getinstattr__(Class, A, '__getinstattr__')
which finds Class.__getinstattr__ and binds it to A (successfully),
yielding the bound method A.__getinstattr__ which serves instances of A
the same way that getinstattr serves instances of Class.

B.__protos__ = [
	readattr,	# useless because B's __dict__ is empty
	lambda o,k,d=A,r=readattr: r(d,k),	# delegate to baseclass A
	lambda o,k,g=getinstattr: g(o,k),	# B is a Class
	_getattr ]

B gets a .__getinstattr__ the same way A did (note that its delegation to A
uses readattr, not getattr, so it doesn't see A.__getinstattr__): the
result is a bound method, with B as the class, waiting for obj and key args.
B also gets .fred from readattr(A, 'fred'): because this was readattr,
this is just A.fred without any fanciness.

b.__protos__ = [
	lambda o,k,g=B.__getinstattr__: g(o,k)	# b is a B
	_getattr ]

When we look up b.fred, we ask B.__getinstattr__(b, 'fred') which is
just Class.__getinstattr__(B, b, 'fred'), which calls getattr(B, 'fred')
and gets B.fred, which __getinstattr__ then duly binds to f.

If we ask for b.__getinstattr__, we call B.__getinstattr__(b, '__getinstattr__')
which is Class.__getinstattr__(B, b, '__getinstattr__'), which asks for
val = B.__getintattr__, which it finds, and then tries to val.bind(b),
which probably fails so we return B.__getinstattr__ as
b.__getinstattr__.  Of course, if we do some magic which lets it do some
further binding, this bind might succeed, leaving us with
	lambda k: Class.__getinstattr__(B, b, k)

Did that help the clarity any ?

> I'm really not sure how your proposal differs from current Python anymore,
it's much more flexible ?

I've only introduced the sorts of entries in __protos__ that would
implement the behaviours we're used to: but there is nothing to stop us
putting all manner of weird stuff into __protos__ to implement other
attribute fetching schemes not currently feasible in python.

It's also unfinished in the sense that I suspect it's unbootstrappable,
at least in its present form.

> You *have* suggested that attributes are fetched from an object in
> different ways depending on whether they are found in a __classes__
> search or a __bases__ search

well, not `on whether they are found in a ... search' but `on the
fetching tool that succeeded', which may be emulating the instance/class
protocol or the class/base protocol, in which case the tool doing the
job has done it differently, which is its right.

	getattr, on the other hand, does *no* munging,

no matter what proto gives it an answer, it takes that answer.

For B, the proto which gave it B.fred was readattr, which does nothing
special.  For b, the proto which gave it b.fred was B.__getinstattr__,
which does do something special.  That's all the difference.  If the
proto that gives me some attribute does hairy things, that's its
business.  It *isn't* getattr's business.

Passing thought:

class Class:
	def __getinstattr__(self, obj, key):
		if key == '__class__': return self

		val = getattr(self, key)
		try: return val.bind(obj)
		except (AttributeError, BindingError): return val

this would give the object a sensible __class__ attribute if its
__dict__ didn't have key '__class__'.  It would also ensure that an
object whose __dict__ doesn't have a __class__ is going to find the
class that first serves it as a class (in its __protos__ list) - rather
than the __class__ attribute of that first class ...