[Twisted-Python] Components
Phillip J. Eby
pje at telecommunity.com
Fri Feb 27 07:58:23 MST 2004
At 12:40 AM 2/27/04 -0500, Glyph Lefkowitz wrote:
>Considering that this is a rather uncommon use-case, can it be made into
>the non-default Interface class? Or an attribute/feature of the default
>one, such as
>
> class MyAbstractSub(IMyAbstract.ABC):
> pass
>
> class DontUseABC:
> __implements__ = MyAbstractSub.Interface
Hmm. I suppose it's possible I could have a 'protocols.Abstract' that
could be subclassed in place of Interface, if you wanted to use the ABC
style. But then, couldn't you also use an 'adapt()' method, i.e.:
foo = IWhatever.adapt(bar)
?
> > Again, this would be easily solved by a Twisted-specific subclass of
> > protocols.InterfaceClass, and I don't see that doing it is necessarily a
> > bad thing for either Twisted or PyProtocols, although it may be that it
> > should be considered simply a transitional, backward-compatibility thing.
>
>The __call__ hack, for me, is more than just syntactic sugar, or a
>"transitional, backward-compatibility thing". It's fundamental to my
>personal use of the component system. One-argument callables are used
>_everywhere_ in Twisted, thanks mostly to Deferreds, but also because
>"thing which takes one argument" is a very convenient interface for
>using for a variety of different kinds of processing. An idiom I find
>tremendously convenient (even, perhaps especially, when debugging) is
>"return foo.doSomethingDeferred().addCallback(IWhatever)". IWhatever is
>sometimes a variable, too - and it's quite common to use 'str', 'int',
>or 'list' there instead of an interface.
>
>Of course, I like the syntactic sugar quite a bit, too :). It's
>self-documenting. When I have run tutorials for others on the use of
>components, showing them an error on x.what(), and then success on
>IWhatever(x).what() is enlightening. Previously, our analogue of
>"adapt", "getAdapter", was difficult to explain.
Certainly, the convenience is tempting. I worry more about the lack of
commonality between 'IFoo(x)' and 'adapt(x,IFoo,somedefault)', from a
presentation standpoint.
I think I'd like to hear some opinions from some existing PyProtocols users
who aren't Twisted users before I "pronounce" on this.
>(BTW: a common idiom for using interfaces is to scope method names. An
>optimization I want to add to Twisted at some point is stateless
>interface-method-call syntax which can do this efficiently, something
>like: IWhatever.what(x). I believe you should be able to avoid a bunch
>of Python object allocation overhead by doing it that way. Does
>PyProtocols provide any such facility?)
Not at the moment. What I'd like to do at some point is add
single-dispatch generic functions, though. Then
class IWhatever:
def what(self,...):
....
would be the default behavior for IWhatever.what(x), and you would define
implementations via something like:
def what_foo(self,...):
...
IWhatever.what.add(what_foo, forTypes=[Foo])
> > Actually, if I understand correctly, these mostly sound like things
> outside
> > PyProtocols' scope. peak.binding and peak.config implement some of this
> > stuff by defining various interfaces they want, and using PyProtocols to
> > adapt things to those interfaces. But that's entirely independent of
> > PyProtocols itself.
>
> > In other words, PyProtocols isn't tightly coupled to a component
> > architecture, but is instead a convenient base for building component
> > architectures.
>
>Perhaps we should be discussing Twisted using PEAK, then? I don't want
>to use half a component system and implement the other half myself.
Okay. The main issue there is going to be that PEAK is definitely still
alpha, although the core CA stuff is *beginning* to approach PyProtocols'
stability. But it's nowhere near PyProtocols in documentedness, of course.
>Maybe you can come up with a counterexample, but it seems to me that the
>benefit of a common protocol system would be lost without the use of a
>common component model.
I don't really see it that way. Right now, using PyProtocols, one can
write components that play in both Zope and PEAK's component
architectures. (Granted, that's to some extent because Zope changed their
CA to be more like PEAK, with interfaces for walking to parents and getting
config data out of them.)
Not that I'm trying to talk you out of using PEAK's CA, mind. I'm just
saying that if you have radically different requirements, you might prefer
to build your own. If it's built atop PyProtocols, it'll be even easier to
integrate your CA with other CAs if people have a need or desire to do so.
> > Let's take a specific example: you mentioned locating nearby services by
> > interface. peak.config does this with two interfaces: IConfigSource and
> > IConfigKey:
>
>Woah there, sparky! That looks a lot like the earlier documentation I
>was having trouble with. A brief example, maybe? :)
Okay, let's say I want to get a hold of the event loop I should be a
participant of...
class SomethingThatNeedsEvents(binding.Component):
eventLoop = binding.Obtain(events.IEventLoop)
Okay, that's it. That's the brief example. :) Seriously, this class is a
component that, when used as part of a larger application, will
automatically find the IEventLoop it's supposed to be using. It may be
defined in an .ini file, like this:
[Component Factories]
peak.events.interfaces.IEventLoop = "peak.events.io_events.EventLoop"
This says that when somebody asks for an IEventLoop, we will look at the
requester's "service area" (a specially designated parent component), and
if the service area doesn't already have one, we'll create a "singleton"
instance of the peak.events.io_events.EventLoop class.
I say "singleton" in quotes because PEAK avoids true singletons like the
plague. Services are homed in a "service area", so there is usually one
service instance per service area. Typically, an app will do all its work
in a single service area, but sometimes there are reasons to have
additional service areas.
The other way you can make a service available (besides configuration for a
service area) is to "offer" them from other components. For example:
class SomethingWithACustomEventLoop(binding.Component):
eventLoop = binding.Make(MyEventLoopClass, offerAs=[events.IEventLoop])
Each instance of this component will have its own private event loop
instance. In addition, any child component of an instance of this class
will receive the instance's event loop whenever it requests an IEventLoop
service. So, if an instance of the first example class is made a child of
an instance of this second example class, it will end up iwth a
MyEventLoopClass instance as its eventLoop attribute.
Is this the sort of thing you're asking about?
> > By the way, though, I don't know what you mean by "default adapter". Do
> > you mean the adapter for type 'object', perhaps? I can't imagine why
> > somebody would care about that, though.
>
>I mean the adapter for type "Thing", mostly. The general idea being
>that you want to override what happens before someone picks up a
>"normal" object when they're under the influence of a particular
>enchantment. It is difficult to specify rules for which enchantment's
>hook takes precedence, so authors get into fights by specifying
>ever-lower numbers.
Ah. That sounds like something that calls for a more explicit chain of
responsibility that's not adapter driven. That is, a component
architecture issue. Adaptation should be something you do to elements in
the chain of responsibility in order to see if they want to participate in
the current event. But maybe I'm misunderstanding something about your use
case.
>Really, an interface specification / lookup system is a pretty basic
>part of a system which uses it, on par with a function calling
>convention. I want to know _exactly_ what goes on when I use it; I
>never want to be surprised by a weird component getting looked up when
>that wasn't what I intended. With so much indirection in place that's
>too easy already.
There's really an easy solution to that. If you want to be sure there's no
confusion, define a new interface when you have a new use case. And if an
old interface mostly fits, you can make the new one a Variation of it or
declare it to be implied by the old one.
More information about the Twisted-Python
mailing list