metaclass doc

Edward Welbourne eddy at vortigen.demon.co.uk
Wed Jan 19 18:40:11 EST 2000


G>>> Have you read my (very old) metaclasses article?
G>>> ... syntax ... slightly different ... the rest is the same.
E>> Non-trivial differences exist ...
G> Like what?

  * a generator creates the object before the suite gets executed; so it
     * only gets, as keywords, any things the interpreter feels like
       feeding it - e.g. __name__, __file__ - that are known at the
       start of the generator statement (it has, of course, the option
       of discarding those, filtering them or adding to them); and the
       name of the new object isn't anything special, except in so far
       as the interpreter *has* provided __name__
     * gets to hang functionality methods off it that influence what
       happens when you do, e.g., the assignments in the suite
     * gets to initialise any attributes it wants to on the object,
       which the suite will be able to read as local variables (it may
       also be able to modify them, if the functionality methods provide
       for this)
     * and the namespace in which the suite gets executed borrows from
       the objects used as bases, so sees their attributes as (readable)
       variables (whether it can modify or - more likely - hide these is
       as for stuff set by the generator)

  * the interpreter is no longer obliged to *know about* the names
    __class__, __bases__, __dict__ and __getattr__ (nor about the added
    names, __lookups__, __meth__, __data__), let alone pay any attention
    to them when deciding how to handle [e.g. call] an object [but it
    may need to know about a new name, __dir__, to implement dir() on
    objects for which __dict__.keys() may be inadequate; but I am
    inclined to argue for that anyway - such objects already exist]

  * a generator provides one object as the generated object and another
    object [usually borrowing attributes from and modifying attributes
    on the generated object] in whose namespace to run the suite,
    allowing folk to build things with (typically) greater liberty in
    what can be done *during* the suite as opposed to what can be done
    to the object *thereafter*: e.g., setting a type constraint on it
    which can't, subsequently, be subverted by simply setting a more
    liberal type-constraint in its place.

I grant the business of having two objects may be avoidable, but only at
the expense of having the interpreter see existing namespaces as
  * *four* distinct types for which it has to do
  * attribute lookup in *six* different ways (built-in attribute,
    dictionary, borrow, inherit, getattr, implicit import, any others ?)
    with each type supporting only some of these
  * calling in *three* incompatible ways (it's a class and we have a
    burried way of calling it that isn't the value of it.__call__; it's
    an instance, so it.__call__; it's a module or package so refuse)

where the generator idiom provides for *one* type on which attribute
lookup is *always* done by calling the function packaged by that type
(with one argument, the attribute name); and calling a namespace-object
is *always* done by looking up the object's __call__ attribute and
calling that (if its type is function, you now know how to; otherwise,
recurse).  That the function called (to do lookup) differs (internally)
according to the flavour (which used to be type) of the namespace
involved is a matter the generator deals with and the interpreter
doesn't have to know - or care - about.  [For __call__, read (in less
severe degree) any other magic name.]

This doesn't rule out talking me into settling for metaclasses (but know
that, now I've understood how *good* they are, I *want* safe tunnels) in
place of generators, it's just that I *really* don't like a whole lot of
discriminatory practices that are going on, like the fact tha defining
__call__(*args, **what) in a module doesn't make the module callable:

-- Begin: trial.py
def __call__(*args, **what):
    print args, what
-- End: trial.py

Python 1.5.2 (#5, Oct  4 1999, 13:36:16)  [GCC 2.7.2.3] on linux2
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>> import trial
>>> trial(1, 2, 3, hello=4, goodbye=5)
Traceback (innermost last):
  File "<stdin>", line 1, in ?
TypeError: call of non-function (type module)

I am *offended* by that - I take it *personally* (you don't have to).
Someone drummed intolerance of unfair discrimination into me *young*.

Consider the implications of defining __getattr__(key), __delattr__(key)
and __setattr__(key, value) in a module and having folk unable to mess
with its contents other than in so far as the last two permit.  Some of
the folk on types-sig might care *a lot* about that.

--

I am puzzled by one thing about metaclasses; I can't quite express it
clearly (and you might have to read init for call and rearrange
somewhat), but this is roughly its form:

def A (B): pass

Now, B.__class__ is present, and callable, so suppose it has a .__call__
So if we try to look up B.__call__, don't we get 
lambda *args, **what: B.__class__.__call__(B, *args, **what)
or, rather, *why* don't we ?  (Or: why *isn't* this badly broken ?)

Or, if you have a way out of that, if B.__class__ provides B with
__call__ in the regular way, (and B hasn't done the same for its
instances) B's instances are going to see B.__call__ inherited from
B.__class__, so why don't they then proceed to inherit *this* [with,
once more, `potentially unwelcome' results]

I'm guessing the answer is that inheritance of methods from one's class
contains a special case (forgive me, but I read that as *prejudice*) of
some sort [e.g. the attribute of the class must be an unbound method ?]
that prevents this.  I'm wondering what inconvenience this might
introduce [e.g. when what I *want* is a method which will get invoked
with args (metaclass, class, instance, ...), though only a
meta-meta-class would want to provide such a thing; but drop a layer of
meta (and the first arg) to get a more plausible case ...]  Of course,
the special case may be in `how do I call this' rather than in `how do I
inherit from my class'.  Can you see why I don't like it, either way ?

This is the sort of ugly question that forced me to the conclusion that
a class should have distinct __meth__ and __data__ sub-objects.

After that, I became calm and happy, albeit the entirety of the rest of
the generator proposal got forced on me by it.

I didn't invent generators because I *want* them: I did so because
they're what classes *demand* if namespaces are to be treated ... justly.

	Eddy.
--
Guido: thanks for being good at asking The Right Questions, too.




More information about the Python-list mailing list