metaclass & __slots__

Alex Martelli aleax at aleax.it
Thu Jul 4 12:47:18 EDT 2002


phoebe_1 at att.net (Holden Caulfield) writes:
	...
"""
  I am trying to get a hang of the metaclass and some new features in
"""
Excellent idea!!!  Metaclasses are still under-used, so far.

"""
v2.2. But, I am running into a problem or it is a limitation, I guess.
I just need clarification. Here is the code, it is an extension of
"""
Yes, I fully agree you just need a little bit of clarification -- you
seem to be very, very close to grasping the metaclass issues
fully, and only need to look at them from a slightly different
angle for them all to click into place and make sense to you, I
suspect.  No limitation involved, and, to give the punch line away,
all the problem is that your class MX is calling its superclass's
__init__ too early -- it just needs to delay that a little bit.  But,
let's get into details:

"""
Basically, I am trying to set "property" attributes AND also limit the
attributes set by using "__slots__".

It looks like because the Metalcass statement is executed in the end
of the class statement, hence the class methods seems to have a
"early" binding to the __slots__ variable. Is this behaviour normal?
"""
There is no such thing as a 'Metaclass statement' .  Rather, when your class
statement's body binds an attribute named __metaclass__, as you do here, 
Python knows to use the value of that attribute as your new class
object's type, i.e., metaclass.  (Otherwise, Python uses other ways
to decide what metaclass to use, but they don't matter here).

There's no issue of early or late.  Statements in a class body are
executed one after the other as usual, when the class statement
itself executes, *before* the metaclass comes into play: i.e., in your 
example:

class X(object):
    __metaclass__ = MX
    __slots__ = ['z']
    def __init__(self):
        self.x = 1
        self.z = 2
    def _get_x(self):
        return self.__x
    def _set_x(self,v):
        self.__x = v

two bindings, to names __metaclass__ and __slots__, then three def
statements (which bind function object to names __init__, _get_x,
_set_x).  The results of these bindings go into a dict, so it really makes
no difference in the end which binding was executed earlier and which
one was executed later, unless you bind more than one value to the
same name (in which case the later binding "overrides" the earlier
one) or similar second-order effects, none of which applies here.

All the statements in class body boil down to constructing a dict and
then the metaclass gets to work on that dict.

So, what your MX's __init__ gets as its dict argument:

class MX(type):
    def __init__(cls,name,bases,dict):

is a dictionary with five items, as bound in the class's body.  Add a
"print dict" statement here to double check -- no rocket science
involved, really, although it may SEEM there's some!-)

So, now it's entirely up to your code in the body of MX.__init__ what
happens.  So let's examine what you actually do there to initialize
object cls, which is the class X you're creating:

        super(MX,cls).__init__(name,bases,dict)

so you first delegate everything to type, which is MX's superclass,
with the dict exactly as build by the class statement.  type.__init__
is responsible among other things for building the descriptors as
specified by __slots__, so of course what rules is the value of
dict[__slots__] at this point -- when you call type.__init__ -- not
any changes that you might further make to that entry, right?

        props = {}
        slots = getattr(cls,'__slots__', [])
        print slots
        for v in dict.keys():
            vs = v.startswith
            if vs("_get_") or vs("_set_"):
                props[v[5:]] = 1
        for v in props.keys():
            fget = getattr(cls,"_get_%s" % v, None)
            fset = getattr(cls,"_set_%s" % v, None)
            setattr(cls,v,property(fget,fset))
        slots.append("_%s__%s" % (name,v))
        setattr(cls,'__slots__',slots)

So all of these statements may affect X.__slots__ in some ways,
but since type.__init__ has already worked with its original value,
that in turn has no effect on the descriptors of X.

You do have some effect on the descriptors of X by the setattr
in the for loop in which you instantiate the property built-in, but
that's another issue.


So, if you've followed so far, I hope the solution is reasonably
obvious: what you want to do is alter your dict argument (don't
bother getting or setting attributes on cls -- work with the dict
arguments directly, possibly taking the bases into account if
you need to) and THEN call type.__init__ with the _modified_
dictionary object.

More generally, in your __init__ (whether of a metaclass or
any other class): call the superclass's __init__ AFTER you have
performed modifications to the arguments, if you need to change
the arguments and the superclass's __init__ is going to take
some action based on them.  Metaclasses are no different (in
this or most other respects) from any other class.


Alex





More information about the Python-list mailing list