[Types-sig] Meta-stuff

Gordon McMillan gmcm@hypernet.com
Tue, 8 Dec 1998 15:30:26 -0500


Gordon's Predicated Provisional Proposed MetaClass Pre-Proposal (
Puh-puh-Proposed MC puh-Proposal ), or, How To Punt on the Theory
Stuff:

Observation 1: People use metaclasses to do getattr hooks.

Observation 2: Getattr hooks come in 3, no, 4 flavors:

  a) None (Surprise!)

  b) Vanilla (magic invoked when attr not found by normal means)

  c) Some attrs hidden from normal means, so magic invoked even 
     though attr is in the normal search path.

  d) Complete rewrite of the search mechanism.

Observation 3: Flavors (a) and (b) are normal Python. Flavors (c) and 
(d) are what current metaclasses are used for.

Observation 4: Usage of current metaclass hooks causes Spontaneous 
Brain Explosions.

Goal: Reduce SBEs by 80%.

Technique: Make the attribute search mechanism a Framework, with the 
possibiity to intervene at selected points.

Current mechanism:

  1. if name is "__dict__" or "__class__", return those

  2. look in instance.__dict__

  3. do a class_lookup:

     3a check class __dict__

     3b for base in bases: do a class_lookup

  4 if found:

    4a if a function, bind as method

    4b if a method, rebind

    4c return it

  5 if there's a getattr hook, call it, return the result

Now let's distinguish between "get" (the whole search starting from 
instance through class and bases to the magic __getattr__), from 
"Get" (which is the recursive class_lookup routine), from "GET" which 
is the raw look in a dictionary-or-moral-equivalent routine. (The 
lower the level, the upper the case.) And then there's 
"__GetNonExisting__", or the current __getattr__ hook.

So, very roughly we have:

get(inst, name) is:
  found, obj = GET(inst, name)
  if not found:
    found, obj = Get(inst.__class__, name)
    if not found:
      hook = get(inst, "__GetNonExisting__")
      if hook:
        found, obj = apply(hook, ...)
  if not found:
    raise AttributeError, ...
  if isFunction(obj):
    obj = bind(...)
  return obj
  
and Get(obj, name) is
  found, obj = GET(obj, name)
  if not found:
    for base in obj.__bases__:
      found, obj = Get(base, name)
      if found: break
  return found, obj

and GET(obj, name) is (usually):
  try: attr = obj.__dict__[name]
  except: return (0, None)
  else: return (1, attr)

Now lets turn that into a Framework. 

  1) The object has a chance to override GET (his attributes may not 
     be in a dict name __dict__; indeed, maybe not in a dict at all). 

  2) The class-like object which is the root of the branch being 
     searched has a chance to specify a new search pattern (ie, 
     override "Get"). 

  3) The root class-like object can intervene in any of the above, or 
     in the way the instance is searched (ie, override "get").

Penultimately, let's use a callback whenever an attribute matching 
"name" is found. The callback can say "dandy, we're done", or it can 
say "keep looking". In fact, the callback is responsible for whatever 
binding, wrapping or perversity that needs to be performed. So what 
gets returned to the caller of "get" is actually what the callback 
has picked, accumulated or munged. So, conceivably, by overriding the 
callback, we could chain together all the base class methods of the 
same name (this is a perversity that was asked for on c.l.p. by 
someone who shall remain unnamed - but I could give you a hint, 
if he doesn't pay me off first...).

This scheme also makes it possible to (relatively) straightforwardly 
pull tricks that currently require hiding the real attribute, so the 
magic hook will get invoked.

Also, "bind" is part of the framework, so it can be overridden 
without horsing around with the other stuff.

Finally, let's provide a default implementation of the 
framework that behaves like current Python. Call it the default 
Turtle. Let classes (instances, too?) specify their Turtles if they 
so desire.

Caveat: I have not explicitly dealt with how to handle multiple 
Turtles in the search. I think, with properly executed callbacks and 
temporary objects, it's possible to let them wrap each other's 
results. Whether the resultant object works as expected is another 
question.

Cop-out: I have some REAL work to do; I've hardly touched Just's 
stuff or JimF's stuff, so don't hold your breath waiting for a 
prototype implementation of the PPPMCPP.

and-th-th-th-that's-all-folks-ly y'rs

- Gordon