[Python-3000] ABC's, Roles, etc
Phillip J. Eby
pje at telecommunity.com
Wed May 9 03:57:37 CEST 2007
At 03:52 PM 5/8/2007 -0600, Jeff Shell wrote:
>I have a lengthy post that dissects a major issue that I have with
>ABCs and the Interface definition that I saw in PEP 3124:: it all
>seems rigidly class and class-instance based.
Hi Jeff; I read your post a few days ago, but your blog doesn't
support comments, so I've been "getting around to" writing a
counterpoint on my blog. But, now I can do it here instead. :)
>The cardinal sin I saw
>in the Interface definition in PEP 3124 (at least, at the time I last
>viewed it) was the inclusion of 'self' in a method spec.
That's because you're confusing a generic function and a "method
spec". PEP 3124 interfaces are not *specifications*; they're
namespaces for generic functions. They are much closer in nature to
ABCs than they are zope.interface-style Interfaces. The principal
thing they have in common with zope.interface (aside from the name)
is the support for "IFoo(ob)"-style adaptation.
Very few of zope.interface's design goals are shared by PEP
3124. Notably, they are not particularly good for type checking or
verification. In PEP 3124, for example, IFoo(ob) *always* returns an
object with the specified attributes; the only way to know whether
they are actually implemented is to try using them.
Notice that this is diametrically opposed to what zope.interface
wants to do in such situations -- which is why the PEP makes such a
big deal about it being possible to use zope.interface *instead*.
That is, my own observation is that different frameworks sometimes
need different kinds of interfaces. For example, someone might
create a framework that wants to verify preconditions and
postconditions of methods in an interface, rather than merely
specifying their names and arguments! Using zope.interface as an
exclusive basis for interface definition and type annotations would
block innovation in this area.
PEP 3124 interfaces are therefore explicitly intended to be merely
one *possible* kind of interface, rather than a be-all end-all
interface system. They have many differences from zope.interface,
which, depending on your goals, may be a plus or minus. But you
certainly aren't obligated to *use* them. PEP 3124 merely proposes a
framework for how to use interfaces for method overloading, generic
functions, and AOP.
From the PEP itself:
"""For example, it should be possible
to use a ``zope.interface`` interface object to specify the desired
type of a function argument, as long as the ``zope.interface`` package
registered itself correctly (or a third party did the registration).
In this way, the proposed API simply offers a uniform way of accessing
the functionality within its scope, rather than prescribing a single
implementation to be used for all libraries, frameworks, and
>... 'self' is an internal detail of class-instance implementations.
Again - this is because you're assuming the purpose of a PEP 3124
interface is to *specify* an interface, when in fact it's much more
like an ABC, which may also *implement* the interface. The
specification and implementation are intentionally unified here.
Of course, again, you will be able to use zope.interfaces as argument
annotations to @overload-ed functions and methods, which is the point
of the PEP. Its "Interface" class is merely a suggested default,
leaving the fancier tricks to established packages, in the same way
that its generic function implementation will not do everything that
RuleDispatch or PEAK-Rules can do. It's supposed to be a core
framework for such add-on packages, not a replacement for them.
>It seems to me that Abstract Base Classes and even PEP 3124 are
>primarily focused on classes. But in Python, "everything is an
>object", but not everything is class-based.
>The rest of this post focuses on what `zope.interface` already
>provides - a system for specifying behavior and declaring support at
>both the class and object level - and 'object' really means 'object',
>which includes modules.
Right -- and *neither* "specifying behavior" nor "declaring support"
are goals of PEP 3124; they're entirely out of its scope. The
Interface object's purpose is to support uniform access to, and
implementation of, individual operations. These are somewhat
parallel concepts, but very different in thrust; zope.interface is
LBYL, while PEP 3124 is EAFP all the way.
As a result, PEP 3124 chooses to punt on the issue of individual
objects. It's quite possible within the framework to allow
instance-level checks during dispatching, but it's not going to be in
the default engine (which is based on type-tuple dispatching; see
Guido's Py3K overloading prototype).
zope.interface (and zope.component, IIRC) pay a high price in
complexity for allowing interfaces to be per-instance rather than
type-defined. I implemented the same feature in PyProtocols, but
over the years I rarely found it useful. My understanding of its
usefulness in Zope is that it:
1. supports specification and testing of module-level Zope APIs
2. allows views and other wrapping operations to be selected on a dynamic basis
Since #1 falls outside of PEP 3124's goals (i.e., it's not about
specification or testing), that leaves use case #2. In my
experience, it has been more than sufficient to simply give these
object some *other* interface, such as an IViewTags interface with a
method to query these dynamic "tag" interfaces. In other words, my
experience and opinion supports the view that use case #2 is actually
a coincidental abuse of interfaces for convenience, rather than the
"one obvious way" to handle the use case.
To put it another way, if you define getView() as a generic function,
you can always define a dynamic implementation of it for any type
that you wish to have dynamic view selection capability. Then, only
those cases that require a complex solution have to pay for the complexity.
So, that's my rationale for why PEP 3124 doesn't provide any
instance-based features out of the box; outside of API specs for
singleton objects, the need for them is mostly an illusion created by
Zope 3's dynamic view selection hack.
>My main focus is on determining what Abstract Base Classes and/or PEP
>3124's Interfaces do better than `zope.interface` (if anyone else is
>familiar with that package).
I at least am quite familiar with it, having helped to define some of
its terminology and API, as well as being the original author of its
class-decorator emulation for Python versions 2.2 and up. :) I also
argued for its adoption of PEP 246, and wrote PyProtocols to unify
Twisted and Zope interfaces in a PEP 246-based adaptation framework.
And what PEP 3124 does much better than zope.interface or even PyProtocols is:
1. Adaptation, especially incomplete adaptation. You can implement
only the methods that are actually needed for your use case. If the
interface includes generic implementations that are defined in terms
of other methods in the interface, you need not reimplement
them. (Note: I'm well aware that here my definition of "better"
would be considered "worse" by Jim Fulton, since zope.interface is
LBYL-oriented. However, for many users and use cases, EAFP *is*
better, even if it's not for Zope.)
2. Interface recombination. AFAIK, zope.interface doesn't support
subset interfaces like PyProtocols does. Neither zope.interface nor
PyProtocols support method renaming, where two interfaces have a
method with the same specification but different method names.
3. Low mental overhead. PEP 3124 doesn't even *need* interfaces;
simple use cases can just use overloaded functions and be on about
their business. Use cases that would require an interface and half a
dozen adapter classes in zope.interface can be met by simply creating
an overloaded function and adding methods. And the resulting code
reads like code in other languages that support overloading or
generic functions, rather than reading like Java.
>In my blog post, I also show a dynamically constructed object
>providing an interface's specified behavior. An instance of an empty
>class is made, and then methods and other supporting attributes are
>attached to this specific instance only. Real world examples of this
>include Zope 2, where a folder may have "Python Scripts" or other
>callable members that, in effect, make for a totally custom object. It
>can also provide this same behavior (in fact, I was able to take
>advantage of this on some old old old Zope 2 projects that started in
>the web environment and transitioned to regular Python
And how often does this happen outside of Zope? As I said, I rarely
found it to be the case anywhere else. I replicated the ability in
PyProtocols because I was biased by my prior Zope experience, but
once I got outside of Zope it almost entirely ceased to be useful.
Meanwhile, as I said, PEP 3124 is not closed to extension. It's
specifically intended that zope.interface (and any other interface
packages that might arise in future) should be able to play as
first-class citizens in the proposed API. However, depending on the
specific features desired, those packages might have some additional
integration work to do.
(Note, by the way, that zope.interface is explicitly mentioned three
times in the PEP, as an example of how other interface types should
be able to be used for overloading, as long as they register
appropriate methods with the provided framework.)
More information about the Python-3000