[TransWarp] PyProtocols: adapting classes to instance
Phillip J. Eby
pje at telecommunity.com
Mon Sep 29 11:11:44 EDT 2003
At 02:54 PM 9/28/03 -0500, Ian Bicking wrote:
>I wasn't sure if this is the right place to post questions about
>PyProtocols...
Go for it; I haven't seen a need yet to set up a separate mailing list. In
fact, you're the first person to *ask* anything about PyProtocols that
wasn't already using PEAK. :)
>Anyway, I want to adapt classes to instances of those classes. So you
>could do something like:
>
>class MyValidator(Validator):
> protocols.advise(instancesProvide=[IValidator])
> ....
>
>adapt(MyValidator, IValidator)
># --> MyValidator()
>
>
>This way classes can be used in place of instances in most cases (and
>obviously the classes are written with this in mind). But I haven't been
>sure how to do this.
I don't understand why you want to do this. It seems to me it would be
simpler to use a singleton instance, instead of creating new ones all over
the place. Perhaps you could explain your use case.
Getting the behavior you want isn't difficult, though, so I'll explain it
below. I would still like to know why you want it, though.
>I tried using a metaclass like:
>
>class ValidatorMeta(type):
> def __new__(meta, className, bases, d):
> cls = type.__new__(meta, className, bases, d)
> protocols.declareAdapter(_simpleClassFactory, cls.__provides__,
> forObjects=[cls])
> return cls
>
>def _simpleClassFactory(v, protocol): return v()
Looks good so far. (Of course, if the class constructor accepted two
arguments, you could declare 'cls' to be the adapter without needing the
separate function.)
>(I set cls.__provides__ manually because I wasn't sure how to find what
>protocols an object supports from PyProtocols, but that's an aside)
You could get this info by having your metaclass provide
IOpenImplementor. The metaclass' 'declareClassImplements()' method would
then be called with each declared protocol, e.g.:
class ValidatorMeta(type):
protocols.advise(instancesProvide=protocols.IOpenImplementor)
def declareClassImplements(cls,protocol,adapter,depth):
if adapter is protocols.NO_ADAPTER_NEEDED:
protocols.declareAdapter(lambda o,p: o(), protocol,
forObjects=[cls])
I believe this should do what you want, without needing a __new__ method,
and without needing a __provides__ member.
>Unfortunately that didn't work. I got an error 'Incompatible __conform__
>on adapted object'. I assume that's because Validator defines a
>__conform__ method, which applies to instances (for conforming validators
>to a different interface).
Here's the issue: when you declare an adapter for a specific instance (e.g.
'forObjects=[cls]'), that object needs to support IOpenProvider. The error
message you're getting is from the default adapters that provide
IOpenProvider for most kinds of "singleton" objects (i.e. functions,
modules, and classes). There is a more detailed explanation in the
reference manual; see:
http://peak.telecommunity.com/protocol_ref/protocols-instances.html
That section describes your problem (what happens when you can't use the
default adapters) and what you need to do instead (mix in 'ProviderMixin').
>So, my basic question (if you understand what I'm trying to do) -- how
>should I do it? I might be all backwards, so I'm open to any ideas.
>Add an __adapt__ method to IValidator? Add a __conform__ method to
>ValidatorMeta? I've tried different combinations, but to no avail.
Another relevant excerpt from the manual:
"""class ProviderMixin
If you have a class with a __conform__ method for its instances, but
you also want the instances to support IOpenprovider (so that adviseObject
can be used on them), you may want to include this class as one of your
class' bases. The default adapters for IOpenprovider can only adapt objects
that do not already have a __conform__ method of their own.
So, to support IOpenprovider with a custom __conform__ method,
subclass ProviderMixin, and have your __conform__ method invoke the base
__conform__ method as a default, using supermeta(). (E.g. return
supermeta(MyClass,self).__conform__(protocol).) See below for more on the
supermeta() function. """
In practice, your situation isn't quite what's described here. In your
case, you want to mix ProviderMixin into the metaclass, and you shouldn't
need to do anything special to the class-level __conform__. The metaclass
__conform__ should then be called whenever you adapt the class itself, and
the class __conform__ should be called whenever you adapt an instance.
So, probably your finished metaclasss will look like this:
class ValidatorMeta(ProviderMixin, type):
protocols.advise(instancesProvide=protocols.IOpenImplementor)
def declareClassImplements(cls,protocol,adapter,depth):
if adapter is protocols.NO_ADAPTER_NEEDED:
protocols.declareAdapter(lambda o,p: o(), protocol,
forObjects=[cls])
More information about the PEAK
mailing list