[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