[Zope3-dev] Re: Interface declaration API

Phillip J. Eby pje@telecommunity.com
Wed, 12 Mar 2003 15:43:53 -0500


This e-mail actually contains API-level questions/issues, along with one 
very brief implementation comment in response to a question of yours...


At 02:54 PM 3/12/03 -0500, Jim Fulton wrote:
>It won't fail because it will get an attribute error and return an
>empty interface spec.  Of course, at that point, mutation will be difficult.
>Mutation won't, in general, be possible through security proxies.

While on this subject, can you or someone else involved with the original 
proposal explain why it's useful to have mutable interface specs, 
anyway?  Is there some highly motivating use case for having interface 
specs be able to be lvalues?  Otherwise, it seems much cleaner to me to 
have API functions to do the manipulating.  That is, 
'implementedBy(Foo).add(IBar)' would be 'addImplements(Foo, IBar)'.


>>With descriptors only, you are powerless to do this to anything that 
>>doesn't have the descriptor already present.
>
>When you need one, you can add one. For example:
>
>   classInstanceInterfaces(Foo).set(IFoo)
>
>would add a descriptor to Foo.

Why is this more desirable than say, 'setImplements(Foo, IFoo)'?

> > Data descriptors take
>>precedence over what's in __dict__, and this can make metaclass access 
>>"weird".  Example:
>>class Meta(type):
>>    aProp = property(...)
>>class Class(object):
>>     __metaclass__ = Meta
>>     aProp = property(...)
>>ob = Class()
>>In the above code, accessing 'ob.aProp' will call the __get__ of 
>>Class.__dict__['aProp'], but accessing 'Class.aProp' will call the 
>>__get__ of Meta.__dict__['aProp'].  This means that you can't just write 
>>a '__get__' that looks to see whether it's being called on the instance 
>>or the class in that case.
>
>Why not?  In both of the cases you've mentioned, we know we are being called
>on the "instance" as far as the descriptor is concerned.

Right, but the descriptor *itself* cannot be the bearer of the data, 
because the metaclass descriptor won't know the instance's content.  That 
is, Meta.aProp cannot know what value Class.aProp should have without 
consulting some other data present in Class.  So, you have to have two 
attributes: the "real" attribute, and the descriptor that either computes 
or returns the value of the "real" attribute.  But this is implementation 
detail.


>>Actually, by having InterfaceSpecification instance simply keep a set or 
>>dictionary of the declared interfaces and have a __contains__ method that 
>>delegates to the dictionary, you wouldn't need an explicit cache:
>>def isImplementedBy(self,ob):
>>     return self in implementedBy(ob.__class__) or self in 
>> directlyProvidedBy(ob)
>
>That doesn't work, because you need to do extends checks, not just sameness
>checks.
>
>Consider:
>
>   class I1(Interface): pass
>   class I2(I1): pass
>
>   class C: implements(I2)
>
>   I1.isImplementedBy(C())
>
>The answer needs to be true, even though I1 isn't in the interface declaration
>for I2.
>
>Of course, the interface specification could include a set of the interfaces
>in the specification and all of the interfaces that they extend.

Sorry; I meant to state the latter, but it got left out.

This brings me to another API-level question...

Is it necessary or desirable to have IInterfaceSpecifications represent 
__iter__() differently from flattened()?

That is, given:

class I1(Interface): pass
class I2(I1): pass

spec = InterfaceSpecification(I2)

is it necessary that:

list(spec) == [I2]

Or is it sufficient if:

I2 in list(spec)

That is, is it acceptable for an InterfaceSpecification to simply list its 
contents in flattened form?

The current (outdated) spec on the Wiki says that __contains__ returns true 
"if the given interface is one of the interfaces in the specification and 
false otherwise".  This doesn't address extends().

My own inclination is that the most desired semantics of 
IInterfaceSpecification are actually as follows:

class IInterfaceSpecification(Interface):

     def __iter__():
        """Iterate over all IInterface objects that would be considered a 
member of this spec
           by __contains__()"""

     def __contains__(interface):
        """Return true if 'interface' is a member of any specification
           aggregated by this one, or is this specification.  'interface' must
           implement IInterface."""

class IInterface(IInterfaceSpecfication):
     ...

By implication, therefore, 'extends()' would not then be strictly necessary 
in IInterface, since 'IFoo in IBar' would be true if IBar is or extends 
IFoo.  (It might be useful to keep it around anyway.)

If an interface spec (declaration? set?) doesn't have to distinguish 
between iter() and flattened(), its semantics are much simpler, and its 
__iter__/__contains__ are now symmetrical.  Implementing mutable interface 
specs is also cleaner, because they can simply loop over the things being 
added to them, and add them to an internal dictionary.

This brings to light another point of unclear semantics in the current 
spec, as I understand it.  The 'add()' and 'remove()' methods do not say 
anything with respect to extends()-ness.  That is:

s1 = InterfaceSpecification(I2)
s1.add(I1)  # is I1 added?  it is, after all "contained" by s1 already...
s1.remove(I1)  # now what happens?