Question about accessing class-attributes.

Bjorn Pettersen BPettersen at NAREX.com
Thu Apr 24 15:35:02 EDT 2003


> From: Alex Martelli [mailto:aleax at aleax.it] 
> 
[...]
> >
> >   class AbstractBaseCounter(object):
> >       __InstCount = {}
> >       def InstCount(cls):
> >           return AbstractBaseCounter.__InstCount[cls]
> >       InstCount = classmethod(InstCount)
> >
> >       def __init__(self):
> >           cls = self.__class__
> >           if cls == AbstractBaseCounter:
> >               raise TypeError("Attempt to instantiate abstract base
> > class")
> >           d = AbstractBaseCounter.__InstCount
> >           if cls in d:
> >               d[cls] += 1
> >           else:
> >               d[cls] = 1
> >
> >   class A(AbstractBaseCounter):
> >       def __init__(self):
> >           AbstractBaseCounter.__init__(self)
> >
> >   class B(A): pass
> >
> >   a1=A()
> >   a2=A()
> >   b1=B()
> >   b2=B()
> >   print B.InstCount()
> 
> comparing the complication of this one, and the simplicity of 
> the custom metaclass I posted and you snipped, PLUS the minor 
> risks such as "B overriding __init__ and forgetting to call 
> its superclass's"(:-), I think the custom metaclass is 
> vindicated;-).

Oh, no argument it's easier. And although the above has problems,
metaclasses have their own set of problems, e.g.:

For reference:
>>> class metaIC(type):
...     def __new__(cls, name, bases, dict):
...         dict['InstCount'] = 0
...         return type.__new__(cls, name, bases, dict)
...     def __call__(cls, *args, **kwds):
...         cls.InstCount += 1
...         return type.__call__(cls, *args, **kwds)

and the (now canonical) metaMetaBunch (munched version [my apologies],
real version here:
http://mail.python.org/pipermail/python-list/2002-July/112007.html):

 class metaMetaBunch(type):
    def __new__(cls, classname, bases, classdict):
        def __init__(self, **kw):
            for k in self.__dflts__: setattr(self, k, self.__dflts__[k])
            for k in kw: setattr(self, k, kw[k])

        newdict = {'__slots__':[], '__dflts__':{}, '__init__':__init__}

        for k in classdict:
            if k.startswith('__'):
                newdict[k] = classdict[k]
            else:
                newdict['__slots__'].append(k)
                newdict['__dflts__'][k] = classdict[k]

        return type.__new__(cls, classname, bases, newdict)

Then define a hierarchy:

 class Counted(object):
	__metaclass__ = metaIC
	
 class Point(object):
	__metaclass__ = metaMetaBunch
	x = 0
	y = 0
	
 class CountedPoint(Counted, Point):
     def __init__(self):
         super(CountedPoint.self).__init__(self)
		
 cp = CountedPoint()
 print cp	

we get:

 Traceback (most recent call last):
  File "M:\python\cpoint.py", line 36, in ?
    class CountedPoint(Counted, Point):
  File "M:\python\cpoint.py", line 6, in __new__
    return type.__new__(cls, name, bases, dict)
 TypeError: metatype conflict among bases

not exactly clear _what_ the conflict is, i.e. it's not that the meta
classes must have a common base (both isinstance(x, type), it's not that
type.__new__ should perhaps be super(x, type).__new__, it _is_ that the
metaclasses aren't related by inheritance. Trying to fix this by, e.g.
making metaIC a subclass of metaMetaBunch (and changing the __new__ call
appropriately), gives you:

Traceback (most recent call last):
  File "M:\python\cpoint.py", line 38, in ?
    class CountedPoint(Counted, Point):
  File "M:\python\cpoint.py", line 25, in __new__
    return metaMetaBunch.__new__(cls, name, bases, dict)
  File "M:\python\cpoint.py", line 20, in __new__
    return type.__new__(cls, classname, bases, newdict)
TypeError: multiple bases have instance lay-out conflict

which means you're stuck (?)

> > which happens to work...? I've verified that
> > AbstractBaseCounter.__init__ gets called for B(). Is this
> > correct/documented/guaranteed/works with MI/multiple args/old-style
> > classes, etc., etc.?
> 
> well, you COULD get dual calls to that __init__ in presence of
> "inheritance diamonds" -- using super may be safer.

True, although for general use, super might have more issues than it's
worth...

-- bjorn
 





More information about the Python-list mailing list