Thank Gabriel, That sounds exactly right and your work-around provides confirmation. This code is an expedient solution for us so I expect we'll go with forcing a slot update. Hopefully the library concerned is going to fix the problem in a future release.
Cheers, Stephen. On Dec 18, 7:40 pm, Gabriel Genellina <[EMAIL PROTECTED]> wrote: > On 18 dic, 12:08, "[EMAIL PROTECTED]" > > > > <[EMAIL PROTECTED]> wrote: > > We are trying to monkey-patch a third-party library that mixes new and > > old-style classes with multiple inheritance. In so doing we have > > uncovered some unexpected behaviour: > > > <quote> > > class Foo: > > pass > > > class Bar(object): > > pass > > > class Baz(Foo,Bar): > > pass > > > # Monkey-patch Foo to add a special method > > def my_nonzero(self): > > print "my_nonzero called" > > return False > > Foo.__nonzero__ = my_nonzero > > > b = Baz() > > > print "doing the test on Baz(Foo,Bar). Should return false" > > if b: > > print "true" > > else: > > print "false" > > </quote> > > > Produces this output: > > > doing the test on Baz(Foo,Bar). Should return false > > true > > > With some experimentation it is clear that this behaviour only occurs > > when you combine new+old-style multiple inheritance, monkey-patching > > and special methods. If Foo and Bar are either old or new-style it > > works. calling b.__nonzero__() directly works. Defining __nonzero__ > > within Foo works. > > > I know this level of messing with python internals is a bit risky but > > I'm wondering why the above code doesn't work. > > I think I can barely explain what happens here (but I may be > absolutely wrong! Please someone with greater knowledge of Python > innards correct whatever is wrong on my description!) > > type objects contain "slots" (function pointers) corresponding to the > "magic" methods like __nonzero__ (this one is stored into the > nb_nonzero slot). new-style classes are types; old-style classes are > not (they're all instances of classobj). > The slots are populated when a new type is created (e.g., when > creating a new-style class) and are updated when a magic attribute is > set onto the type. By example, setting the __nonzero__ attribute on a > new-style class updates the nb_nonzero slot. old-style classes just > store the magic attribute in its __dict__. Note that if you patch Foo > *before* defining Baz, it works fine, because Baz sees the magic > attribute and can populate its nb_nonzero slot when the new type is > created. > > When you define Baz, neither Foo nor Bar have a __nonzero__ at this > time, so the nb_nonzero slot on Baz is empty. > Later, when you alter Foo, Baz cannot notice it (Foo has no way to > notify Baz that something changed). > > If Foo were a new-style class, things are different: new-style classes > maintain a list of subclasses, and the subclasses can then be notified > of changes. In particular, setting a magic attribute on a base class > notifies all its subclasses, and the corresponding slots are updated. > > The problem seems to be exactly that: old-style base classes can't > notify its derived new-style classes when magic methods are added, so > the corresponding slots aren't updated. Always asking the base class > whether it has a magic method or not would slow down all method > lookups. > > To force a slot update: > > py> Baz.__nonzero__ = "xxx" > py> del Baz.__nonzero__ > py> bool(b) > my_nonzero called > False > > (setting or deleting __nonzero__ triggers the slot update) > > This is *why* it happens. How to avoid this... well, don't do that in > the first place :) Or try to patch the base class *before* the new- > style derived class is defined. Or replace the derived class with a > new version of itself (once the base was patched). Or, if you know all > the derived classes, force a slot update on them as above. > > -- > Gabriel Genellina -- http://mail.python.org/mailman/listinfo/python-list