[Zope3-dev] Re: Interface and Adapter Demo

Tres Seaver tseaver at zope.com
Sun Jun 27 21:18:46 EDT 2004


Christian Robottom Reis wrote:
> On Sun, Jun 27, 2004 at 09:12:55AM -0400, Stephan Richter wrote:
> 
>>For statistical reasons we often want to know the size of a file. However, it
>>would be clumsy to implement the size directly in the file object, since the
>>size really represents meta-data. Thus we create another interface that
>>provides the size of something.
> 
> 
> Just an orthogonal thought: is it really *that* clumsy to implement the
> size as part of the file object? Or it is more of a design decision that
> for certain situations makes sense and for others not?

Stephan's example is perhaps slightly contrived;  the more usual 
rationale would be that you want to vary the policy (represented by 
ISize), and don't want your "mechanism" class to depend on specific 
policy implementations.

Imagine, for example, that the 'getSize' method might need to take 
character encodings into account, or collapse runs of repeated 
whitespace, or other such policy choices.

>>  >>> class ISize(zope.interface.Interface):
>>  ...
>>  ...     def getSize():
>>  ...         'Return the size of an object.'
>>  ...
>>
>>Now we need to implement the file. It is very important that the object states
> 
> 
> maybe s/very important/essential/
> 
> This was one thing that I didn't take into account initially -- the
> registry is designed to work exclusively with Interfaces.

In Zope3, there is a mechanism *outside* the class for making these 
assertions, which may be the source of your confusion.

>>Next we implement an adapter that can provide the 'ISize' interface given any
>>object providing 'IFile'. By convention we use '__used_for__' to specify the
>>interface that we expect the adapted object to provide, in our case
>>'IFile'. However, this attribute is not used for anything.
> 
> 
> Is the convention to use a tuple if used for more than one Interface, or
> would that be a `bad thing'?

I think the "convention" is not that conventional;  however, the key to 
the idea is that the adapter *depends* on a particular interface in the 
object being adapted;  it would be comparatively rare to be able to 
adapt from more than one source interface to the target interface.

>>Again by convention, the constructor of an adapter takes one argument, the
>>context. The context is an instance of 'File' 
> 
> perhaps ... in this case is an instance ...

The only real guarantee is that the 'context' argument will implement 
the "source" interface for which the adapter is registered.  It should 
*not* expect to use attributes or methods of the 'context' which are not 
exposed through that interface.

>>to extract the size from. Also by convention the context is stored in an
>>attribute 'context' on the adapter. The twisted community refers to the
> 
> 
> attribute named 'context' perhaps.
> 
> Does it make sense to have adapter-specific names, such as naming the
> attribute `file' in this case, given the improved understandability of
> the adapter's code, or is the adapter's code usual too short for it to
> matter? 

I would stick with 'context' as the "canonical" form.  If you have an 
adapter method for which the name is confusing, then create a local 
variable which provides more clarity:

   def getFileContext(self):

       file = self.context
       # ....

<snip>

>>Register an adapter that adapts from 'IFile' to 'ISize'. The first argument to
>>the registry's 'register()' is a list of original interfaces. In our cause we
> 
> 
> s/is a list/method is a list/ perhaps
> 
> 
>>have only one original interface. 
> 
> I find this section a bit heavy on required concepts. You might want
> to use something from this alternative summary:
> 
>     The registry keeps a map of what adapters implement an interface on
>     behalf of other interfaces. To register FileSize, an adapter that
>     adapts IFile-providing instances to the ISize interface, we use the
>     register() method::
> 
>         >>> registry.register([IFile], ISize, '', FileSize)
> 
>     The first argument is a list of original interfaces; in our case,
>     FileSize only adapts instances providing one interface, IFile.
> 
>     Using a list makes sense when the same adapter is available for
>     multiple interfaces -- in our case, it might make sense to adapt
>     other `sizeable' objects, such as IString and even IDistance.
> 
>     The second argument is the interface the adapter provides, in our
>     case 'ISize'. 
> 
>     [...]
> 
>>The third argument in the name of the adapter. Since we do not care
>>about names, we simply leave it as an empty string. 
> 
> Conventionally (perhaps Zope-conventionally <wink>), what are these
> names useful for?

Registerning alternative adapters for a given target interface;  the 
default name is the empty string.

>>us to get an adapter instance by simply calling 'ISize(file)'. To make use of
>>this functionality, we have to register our registry with some hook mechanism.
> 
> Magical, magical adapter_hooks.

Only if you want magical, magical syntactic sugar. :)

> It took me a while to realize that there
> was something else I needed to do to get interfaces to use the registry
> to determine how to adapt a certain instance. In hindsight, that makes
> sense: you might have multiple registries, registries used in certain
> situations and others in others, but it's completely non-obvious at
> first sight.
> 
>     .. we need to add our registry to the adapter_hooks list, which is a
>     member of the adapters module. This list stores a collection of
>     callables that are automatically invoked when IFoo(obj) is called;
>     their purpose is to locate adapters that implement an interface for
>     a certain context instance. 
> 
> perhaps.
> 
> If we *must* write our own adapter hooks, then it should say so
> somewhere; since here is as good a place as any, I suggest:
> 
>     You are required to implement your own adapter hook; this example
>     covers one of the simplest hooks that use the registry, but you
>     could implement one that used an adapter cache or persistent
>     adapters, for instance.
> 
> 
>>  >>> size = ISize(file)
>>  >>> size.getSize()
>>  7
>>
>>That's it. I have intentionally left out a discussion of named adapters and
>>multi-adapters, since this text is intended as a practical and simple
>>introduction to Zope 3 interfaces and adapters. You might want to read the
>>'adapter.txt' in the 'zope.interface' package for a more formal, referencial
>>and complete treatment of the package.
> 
> 
> Note that adapter.txt makes my brain feel soft (and someone else has
> said the same thing to me -- though perhaps just to make me feel
> better). It seems to be at points trying (and succeeding <wink>) *on
> purpose* to confuse me, via the use of integers (what is 12?) in place
> of "context".  This is actually compounded by the adapt_0_to_42 adapter
> hook exemplified in interface.py -- I was almost sure adapter_hooks were
> what I wanted but the example just didn't make sense -- it doesn't even
> use the iface argument, and nowhere does it suggest I might want to use
> the registry.
> 
> It would help tons just to add the simplest adapter hook that used the
> registry as an example, and point to it -- many people will need
> something more specific, but there's at least something to start from.
> 
> Thanks a lot for taking the time to write this.

Yep, seconded.

Tres.
-- 
===============================================================
Tres Seaver                                tseaver at zope.com
Zope Corporation      "Zope Dealers"       http://www.zope.com



More information about the Zope3-dev mailing list