[Zope3-dev] Interface declaration API

Phillip J. Eby pje@telecommunity.com
Tue, 11 Mar 2003 15:41:28 -0500


At 02:46 PM 3/11/03 -0500, Jim Fulton wrote:
>Subclassing is done to reuse implementation. If interfaces were
>inherited, then you would be unable to subclass a class without
>also promising to implement the base class interfaces.  I've always
>viewed this as an undesireable requirement, but maybe I'm wrong,
>since so many people desire it, ;) So I'm willing to give on this.

Ironically, you then go on to say that 'doesNotImplement()' would be a 
YAGNI.  :)  I actually suggested 'doesNotImplement()' as a way to deal with 
the situation above.  If one wishes to reject base class interfaces, one 
might then do so explicitly.



>Soooooooo ....
>
>Let's suppose we decided that in:
>
>   class C(A, B):
>
>      alsoImplements(IFoo)
>
>The outcome would be that C's instances would implement
>IFoo and whatever A's and B's instances implement.
>
>This is implementable, but with a hack. The hack is that the
>descriptor needs to update itself the first time that it's used. :(
>
>In::
>
>
>   class C(A, B):
>
>      pass
>
>The outcome would be that C's instances would implement
>whatever A's and B's instances implement.
>
>This too requires a pretty heavy hack.  The first time you tried to
>get the instance interface spec for a C, you'd inherit a spec from
>A or B. The inherited descriptor would detect that it was inherited
>and create a new descriptor for C, merging the interfaces from A and
>B.
>
>Finally, with:
>
>   class C(A, B):
>
>      implements(IFoo)
>
>The outcome would be that C's instances would implement
>only IFoo.

I've previously implemented late-binding class descriptors of this kind, 
and I agree that they aren't appropriate for Zope 3.  One potential issue, 
for example, is thread-safety of the object that does the late binding 
(because non-persistent classes are normally shared between threads).

I do notice, however, that your description seems to imply a 
descriptor-based implementation.  If the intention is to encapsulate the 
implementation, it's not clear to me that putting the behavior in 
a  descriptor is the most useful way to go.  For example, 'implementedBy()' 
might look something like this:

def implementedBy(klass):

     spec = klass.__dict__.get('__implements__')

     if spec is None:
         spec = klass.__implements__ = InterfaceSpecification(
             *tuple([implementedBy(c) for c in klass.__bases__])
         )

     return spec

Notice this also has the same thread-safety issue.  Also, you have to have 
*something* that does this, if you want 'implementedBy()' to return a 
mutable object!

So, if you really want to simplify this, I'd suggest going back to 
immutable interface specs.  This would then require an explicit rebinding 
(e.g. klass.__implements__ += IFoo) and wouldn't need a check for whether 
the specs were inherited or not.  You also wouldn't need a descriptor, 
which means that "classic" classes might be better supported.  (I'm not 
positive that any of classic classes' limitations on descriptors are an 
issue here, though.)

In order to preserve encapsulation, you would need to have functions 
like  'addImplements()', 'addProvides()', 'setImplements()', etc.  While 
this expands the number of API functions, it also increases the 
explicitness of such operations (one may grep for 'setImplements()' more 
easily than for 'foo=implementedBy(Bar) ...  multiple lines of code ... 
foo.add(IBaz)'.  That is, one is now required to put the operation in the 
same place with the thing being operated upon.

Doing this would actually give you an implementation benefit to sticking 
with "first in MRO" inheritance semantics for interface declarations, er, 
specifications.

(By the way...  I always stumble on calling them "interface 
specifications".  To me, the interface specification is the "class 
IFoo(Interface):" block, while the "__implements__ = IFoo" is an *interface 
declaration*.  But I may be alone in this.)

Anyway...  what I'm saying is, if you stick with mutable specifications, 
you might as well implement "merged __bases__" inheritance, because there 
is no less implementation complexity.  AFAICT, to reduce the implementation 
complexity from what you described, you have to also make specifications 
immutable, so that it never matters if the specification is inherited or 
not.  Otherwise, you are right back in the same boat of needing to either 
have a lazily-binding descriptor, or a function that acts like one "from 
the outside".