[Python-Dev] Evil setattr hack

Phillip J. Eby pje@telecommunity.com
Tue, 15 Apr 2003 14:45:43 -0400


At 02:33 PM 4/15/03 -0400, Guido van Rossum wrote:
>[Guido]
> >  >I've checked in what I believe is an adequate block for at least
> >  >this particular hack.  wrap_setattr(), which is called in response
> >  >to <type>.__setattr__(), now compares if the C function it is
> >  >about to call is the same as the C function in the built-in base
> >  >class closest to the object's class.  This means that if B is a
> >  >built-in class and P is a Python class derived from B,
> >  >P.__setattr__ can call B.__setattr__, but not A.__setattr__ where
> >  >A is an (also built-in) base class of B (unless B inherits
> >  >A.__setattr__).
>
> > From: "Phillip J. Eby" <pje@telecommunity.com>
>
> > Does this follow __mro__ or __base__?
>
>It follows __base__, like everything concerned about C level instance
>lay-out.
>
> > I'm specifically wondering about the implications of multiple
> > inheritance from more than one C base class; this sort of thing
> > (safety checks relating to heap vs. non-heap types and the "closest"
> > method of a particular kind) has bitten me before in relation to
> > ZODB4's Persistence package.
>
>It is usually impossible to inherit from more than one C base class,
>unless all but one are mix-in classes, meaning they add nothing to the
>instance lay-out of a common base class.
>
> > In that context, mixing 'type' and 'PersistentMetaClass' makes it
> > impossible to instantiate the resulting metaclass, because neither
> > type.__new__ nor PersistentMetaClass.__new__ is considered "safe" to
> > execute.
>
>You're referring to this error message from tp_new_wrapper(), right:
>
>     "%s.__new__(%s) is not safe, use %s.__new__()"

Yep, that's the one.


> > My "evil hack" to fix that was to add an extra PyObject *
> > to PersistentMetaClass so that it has a larger tp_basicsize than
> > 'type' and Python then considers it the '__base__' type, thus
> > causing its '__new__' method to be accepted as legitimate.
>
>Is this because the algorithm in best_base() picks the wrong base
>otherwise?

Yes, at least for Python 2.2.  However, the problem with ZODB4 was only an 
issue on 2.2; on 2.3, PersistentMetaClass *is* 'type', because it is there 
to workaround C layout issues in 2.2 that don't exist in 2.3.  So this is 
probably all moot.

Anyway...  if I recall correctly, even if you got best_base() to pick the 
right base by changing the order of mixing in the classes, you got a 
*different* safety error message; I think it might have been in the 
resulting class, though, rather than in the metaclass.  This was all back 
in November, so my memory is a little hazy.  I think there might have been 
more details in the Zope3-Dev collector issue (#86), but I think Jeremy 
showed that info to you previously and said that it wasn't enough for you 
to understand what the problem was.  I think part of the complexity had to 
do with the fact that one of the types (my subclass of 'type') was a "heap 
type", and PersistentMetaClass was not.

But as you pointed out, subclassing from multiple C bases is a rarity, so I 
don't see any point to following this up further, unless you have some 
perverse desire to have yet another new-style class layout algorithm change 
for Python 2.2.3.  :) It's probably better just to leave my "make it 
bigger" hack in ZODB4, since PersistentMetaClass itself is one big Python 
2.2 backward compatibility hack anyway. <wink>