[PEAK] Some simple adaptation questions with PyProtocols

Phillip J. Eby pje at telecommunity.com
Fri Feb 27 19:37:15 EST 2004


At 07:11 PM 2/27/04 -0500, David Bolen wrote:

>After some experimentation (first forgetting that __adapt__ needed to be
>a static method, and then not realizing that if I got the signature
>wrong PyProtocols is too smart and doesn't even call it :-)), I was
>going with something like:
>
>     class PIN(object):
>
>         def __init__(self, value):
>             self.value = value
>
>         def __adapt__(obj):
>             if isinstance(obj, (str, unicode)):
>                 self.value = int(value)
>             else:
>                 return None
>         __adapt__ = staticmethod(__adapt__)

I'd ordinarily make __adapt__ a classmethod for this situation, so that it 
can be sensibly inherited by a subclass.


>Since I figured I'd want the declaration API available for flexibility,
>I then found that PIN had to inherit from Interface (as an aside, I
>don't think I quite appreciate the subtlty of classes versus instances
>acting as protocols, and the difference between Interface and Protocol
>since I first tried inheriting from Protocol based on its name),

An instance of Protocol is a protocol.  InterfaceClass is a subclass of 
Protocol, so interfaces are protocols.  The tricky bit is that you subclass 
Interface instead of making an instance of InterfaceClass.

In some ways, it might have been better for me to make it work like this:

class ISomething:
     __metaclass__ = Interface

Which then would've been 100% consistent terminology, but then lots of 
people get scared by __metaclass__.



>  but I
>wasn't able to do it without defining a separate protocol object, since
>advise required an instancesProvide, but syntactically with Python I
>couldn't reference myself during the advise call.  Since in this
>scenario there's a 1:1 between the object and the "protocol" I want to
>reference, just defining a dummy class to act as the protocol seems like
>it should be unnecessary.
>
>So using advise was actually slightly more complicated than just
>__adapt__.  Should there have been a way around that?  E.g., I guess I
>just wanted to be able to write:
>
>     class PIN(Interface):
>
>         def __init__(self, value):
>             self.value = value
>
>         def adaptFromStr(kls, obj, protocol):
>             return kls(int(obj))
>
>         advise(asAdapterForTypes=[str, unicode],
>                factoryMethod='adaptFromStr')
>
>which I would have liked as being cleaner (the data types just being
>listed in the advise call).  But advise wanted me to include an
>instancesProvide, even though it's the class itself that is what it
>provides.  Maybe that's what I didn't understand - since adapt works to
>PIN (the class) implicitly, shouldn't advise() assume that the class
>itself is automatically included as an implicit instancesProvide?

'advise()' is a convenience API for classes *and* modules.  You can also do 
'moduleProvides'.

In addition, there's a 'classProvides' which has different meaning than 
'instancesProvide'.  So, there really can't be an assumption like what you 
describe.

Essentially, PyProtocols doesn't have any convenience features for working 
with ABC-style interfaces in the way that you're doing.  The simplest 
workaround for what you want to do is to define a metaclass for interfaces 
like this, that looks at your adaptation methods and makes the appropriate 
declareAdapter/etc. calls.  You could then reuse that metaclass for other 
ABC-style interfaces.


>I can get around this by using the declareAdapter outside of the class,
>and a static method in lieu of a class method (if I wanted to keep it
>all in the class), or a separate function (if I didn't care), as in:
>
>     class PIN(Interface):
>
>         def __init__(self, value):
>             self.value = value
>
>         def adaptFromStr(obj, protocol):
>             return PIN(int(obj))
>         adaptFromStr = static(adaptFromStr)
>
>     declareAdapter(PIN.adaptFromStr,provides=[PIN],forTypes=[str,unicode])
>
>but the docs sounded like advise was the nice high level desirable
>approach

No, it's a "convenience" API.  The 'declareAdapterForX' APIs are the core 
API, and everything else is a convenience shortcut.


>(and I sort of liked having the built-in adaptations all
>declared internally to the class),

That's not really supported; advise() in a class can declare ways that the 
class can do adaptations to specified protocols, but it does *not* let you 
say anything about adapting *to* that class.  There's no provision in the 
API for it at all, and my expectation would always be that you'd do it more 
or less as you've done it above, with the personal difference that I'd 
never be actually putting the behavior into the interface, and would 
instead use separate functions.


>so is needing to do this an
>indication that I'm doing something strange?

Not really.  I have a fair amount of code that looks like this in PEAK, 
just not using the interface to contain its own adapters.  But I have 
*lots* of adapter functions, as opposed to classes.




>I'm guessing that the final case is probably what I'll end up going
>with, but am I missing a simpler way?

Nope.


>I realize this maybe this isn't
>the most typical use (where the object pretty much knows what it can
>adapt to - at least for the cases I care about now) but it does seem
>like a useful use for adaption in the original vein of PEP 246.

And you'll notice you can still use the PEP 246 approach.  It's just that 
PyProtocols doesn't make your use case dramatically easier than PEP 246 
provides for.  Much easier, yes, but not quite as easy as it is in the case 
where you keep implementation separate from interface.  :)




More information about the PEAK mailing list