[PEAK] Some simple adaptation questions with PyProtocols

David Bolen db3l at fitlinxx.com
Fri Feb 27 19:11:29 EST 2004


I've been experimenting with PyProtocols, and have some questions about
what is probably an awfully simple form of adaptation.  I was curious
for opinions on if I should be using the declaration API (and an
IOpenProtocol protocol object), or just stick with __adapt__ at the
current level of adaptation I require.

I guess I'm mostly looking for other opinions and/or comments on my
approach, since while the docs are detailed, they don't necessarily have
much in the way of simple adaptation examples along the lines of what
I'm doing at the moment.

As an attempt at a stripped down example, let's say that I had this
class originally:

    class PIN(object):

        def __init__(self, value):
            if isinstance(value, (str, unicode)):
                self.value = int(value)
            else:
                self.value = value

        ...

That is, the class is a value object for an integer value (in the
actual application it's an object representing an integral value with
additional rules imposed on creation and processing).  The existing
code had to special case __init__ so that the object could be created
both internally (where an existing integer would be used) or
conveniently from external interface points where the incoming value
was more likely to be a string.

But of course, this does the type checking during an explicit
construction of the PIN object, which I wanted to get rid of in favor
of adaptation.  I figured I'd just treat the PIN object itself as my
protocol.  That way, all the external interface points in the app can
just adapt to PIN.  So I'd like the following to happen:

    adapt(PIN(10),PIN)     ->  Yield the original PIN object
    adapt(10,PIN)	   ->  Yield a PIN(10) object
    adapt('10',PIN)        ->  Yield a PIN(10) object

The difference here compared to the examples I did see in the
documentation is that my adaptation function is always yielding the same
object type - e.g., in my current cases I don't want to wrap the object
being adapted (where adapting the string '10' might return something
like a PINStrAdapter rather than an actual PIN), but rather use it to
construct a new basic protocol object, in this case always yielding a
PIN.

Now, while I'll have other cases where objects will support more than
one protocol, in this case I really just want to use PIN as the protocol
itself (e.g., there's a 1-to-1 mapping in functionality and object), so
I'm not sure I'd bother defining an IPIN separately.

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__)

        ...

which pretty much did what I want.  Of course, I'm stuck working fairly
low level and more importantly, I have to code all the possible
adaptations in the protocol object itself, and can't use the declaration
API.  About the only win I'd see here over the prior approach is if the
constructor of PIN took multiple arguments, since adaptation can be more
flexible in converting data from within some other object to those
multiple arguments without burdening the code asking for the adaptation.

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), 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?

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 (and I sort of liked having the built-in adaptations all
declared internally to the class), so is needing to do this an
indication that I'm doing something strange?

I'm guessing that the final case is probably what I'll end up going
with, but am I missing a simpler way?  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.

If you've put up with this message until now, thanks.  I'd appreciate
any thoughts that anyone may have.

-- David



More information about the PEAK mailing list