Yet Another PEP: Query Protocol Interface or __query__

Carlos Ribeiro carribeiro at yahoo.com
Sun Mar 25 22:23:59 EST 2001


I'm thinking about a slightly different approach for the __adapt__ PEP. 
However, I need some help to clarify my ideas - english is not my first 
language, so please be patient :-) I also did my homework, and I have a 
very nice  that works fine for me.

0) Let's start from the scratch (I'm not sure if we're trying to solve the 
same problem)

1) What is a protocol? The simplest definition that I cant think of is: a 
protocol is just a bunch of methods. An object is said to support a 
protocol if it implements all methods of the protocol specification. The 
specification defines the names and parameters of all methods.

2) A 'protocol instance' is the minimal object instance that completely 
supports the protocol.

3) Now we're getting closer. My proposal is to have a method called 
__adapt__ that returns a 'protocol instance'.

4) So __adapt__ must convert a 'object instance' into a 'protocol 
instance'. If the object implements the standard names with the default 
names, this is pretty straighforward - the object could be used as a 
protocol instance.

5) The problem occurs when the object supports the protocol using methods 
with different names. Assume that we have two different protocols asking 
for methods with the same names, but with different semantics and/or 
parameters. An object that supports both protocols will need some way to 
map its internal methods to the ones defined by each protocol.

6) We can solve (5) using a 'proxy', or 'adapter'.

EXAMPLE

Assume that we have these protocols:

a) protocol 'IntegerDivision'
    supports the __div__() method for integer division
b) protocol 'FloatDivision'
    supports the __div__() method for floating point division

#----------------------------------------------------------------------

class Protocol:
   """ empty class that can be used for all protocol adaptation """
   pass

def MakeProtocolDefault(protocol_methods, adaptee):
   """ Returns the default protocol adapter for an object
       The default uses the same name for all methods in the protocol
       and in the object instance that is being adapted
   """
   p = Protocol()
   for pm in protocol_methods:
     om = getattr(adaptee, pm)
     setattr(p, pm, om)
   return p

def MakeProtocol(protocol_methods, object_methods):
   """ Returns the a protocol adapter for an object, mapping
       each protocol_method to a object_method
   """
   p = Protocol()
   for pm, om in zip(protocol_methods, object_methods):
     setattr(p, pm, om)
   return p

class Number:
   """ supports both protocols: 'IntegerDivision' & 'FloatDivision' """
   def __init__(self, p_value):
     self.value = p_value

   def div_int(self, b):
     # integer division
     return int(self.value)/int(b)

   def div_float(self, b):
     # floating point division
     return float(self.value)/float(b)

   def __adapt__(self, protocol):
     if protocol == 'IntegerDivision':
       return MakeProtocol(['__div__'], [self.div_int])
     elif protocol == 'FloatDivision':
       return MakeProtocol(['__div__'], [self.div_float])
     else:
       return None

#----------------------------------------------------------------------

 >>> n = Number(2)
 >>> in = n.__adapt__('IntegerDivision')
 >>> fn = n.__adapt__('FloatDivision')
 >>> in/3
0
 >>> fn/3
0.66666666666666663
 >>> n.value = 5
 >>> fn/3
1.6666666666666667
 >>>

#----------------------------------------------------------------------

Some notes:

a) Please keep this in mind: I tried to keep the implementation as simple 
as possible. I'm not trying to emulate any specific language construct. My 
intention is just to provide a simple 'protocol adapter' layer for Python.

b) I'm using strings to represent protocol names. I fell that this is a 
very simple and extensible approach. For example, the string could be a URL 
pointing to the canonical description of the interface, made using XML.

c) The MakeProtocol helper function matches all methods desired in the 
protocol with the ones implemented in the object. The actual syntax may me 
different (eg passing a single sequence containing the pairs, or passing a 
dictionary), but this is the basic idea.

d) The first parameter of MakeProtocol can be thought of as the 'protocol 
definition'. However I dont have any specific suggestion on this right now 
- thats why I'm using a simple list.

e) The Protocol class is empty; this was done to avoid potential name 
clashes and to ensure that *any* protocol can be adapted using it.

f) A very simple optimization would be to prepare all 'protocol adapters' 
on __init__, returning them inside __adapt__. This can be done if you 
expect a lot of __adapt__ calls.


p.s. I think that it is possible to devise a highly optimized 
implementation. In fact, if all protocols are known at compile time, this 
should make it easier to optimize method calls, but this need to be 
explored more carefully. This does not need to be made right now - as Clark 
pointed out, may be it's better to see if people actually uses this.

pp.ss. It was not my original intention, but we could use this mechanism, 
as exposed in my example, as a way to implement the PEPs that propose 
semantic changes on the numeric types.


Carlos Ribeiro


_________________________________________________________
Do You Yahoo!?
Get your free @yahoo.com address at http://mail.yahoo.com





More information about the Python-list mailing list