[TransWarp] peak.interface doesn't coop with class-attributes that are classic classes

Phillip J. Eby pje at telecommunity.com
Mon May 12 12:46:45 EDT 2003

At 11:24 AM 5/12/03 +0200, Ulrich Eck wrote:
>Hi Phillip,
>while running the unit-tests for our nll-library i found a bug in the
>new peak.interface package.
>the line:
>  mro = type('tmp', (typ,), {}).__mro__
>raises a:
>TypeError: a new-style class can't have only classic bases

Oops, guess that should've been:

mro = type('tmp', (typ,object), {}).__mro__

Actually, I wonder if I should make some kind of cache for mro's of classic 
classes.  Obviously, PEAK itself doesn't use them, but most of the Python 
standard library does, and part of the point of the adaptation subsystem is 
compatibility with third party classes.

Unfortunately, you can't make weak references to classic classes, so a 
registry that adapted them would accumulate references in its cache.

>  # Trivial interface implementation
>+# Helper for Adapt-Protocol
>+class _marker(object): pass
>  class Protocol:
>      """Generic protocol w/type-based adapter registry"""
>@@ -175,9 +178,10 @@
>          try:
>              mro = typ.__mro__
>          except AttributeError:
>-            mro = type('tmp',(typ,),{}).__mro__
>+            mro = type('tmp',(typ,_marker,),{}).__mro__
>          for klass in mro:
>+           if klass is _marker: continue

It's not necessary to stop at _marker or object; in fact this makes it 
impossible to then use a generic 'object' adaptation.  Sometimes I register 
an adapter for 'object' to provide a default behavior, and I expect to do 
it more in future.  I'll want it to work for classic classes, although I 
suppose I could use InstanceType instead.  But, the current MRO calculation 
isn't including InstanceType in the path.  Maybe the classic MRO should end 
with 'InstanceType,object', even though you can't create a class like that.

Yuck.  I don't care much for this any which way.  Classic classes are a 
real pain, and they will be slow for adapter lookups no matter what I 
do.  Unless I do some kind of reverse registration with them; I suppose I 
could have protocols poke the adapters into the classic classes going in, 
and look them up there coming out.  Unlike types, I believe classes are 
guaranteed to be mutable.  If I use the protocol as the key, and the 
adapter as the value, that would actually be an even faster lookup than for 
new-style classes!  In practice, though, that breaks help() on the altered 
class, so I'd have to create a string identifier in place of the actual 
protocol as the key.

However, if I did this, there's an interesting side effect.  I could do it 
to new-style classes, too, as long as they were mutable.  Which would mean 
that in the most common case, finding an adapter would be as fast as a 
Python class attribute lookup; no manual looping over __mro__ needed!

Unfortunately, there are a couple of holes here regarding immutable 
(built-in) types, custom metatypes that disallow __setattr__, and 
persistent types (i.e. from ZODB4 persistent modules).  These won't accept 
(or would behave badly in the context of) setting attributes on them.

But I think there's a solution, that will lead to good performance for PEAK 
components, and also for classic classes, just not for third-party 
new-style classes or built-in types.  I think I can also get those to be 
faster, too, actually, just not on the first failed lookup for a particular 
protocol.  What I'll do is, when registering an adapter or declaring 
implementation for something, I'll first 'adapt()' the subject type to a 
'IInstancesProvide' interface.  The implementation of this for classic 
classes and PEAK classes will poke the adapter data into a unique attribute 
name on the class.  The implementation for 'type', will perform normal 
protocol registration.  PEAK base classes will have a __conform__ that 
handles the poked data, leading to faster successful lookups.

The protocol registries will do as they do now for new-style types, but 
they will also cache the lookups, and they will be WeakKeyDictionary 
objects, because we won't need the registry for classic classes.  Classic 
classes will have their adapters looked up using the poked attributes.

This all actually highlights a hole in the current setup of 
'peak.interface'; specifically, that the declaration mechanisms don't 
double-dispatch like everything else.  That is, the declaration API doesn't 
give the types a chance to participate in how "implements" registration 
takes place.  Fixing that could open up all sorts of interesting possibilities.

For now, though, I suppose I should check in a quick fix.  :)

More information about the PEAK mailing list