[Urwid] possible bug: AttrMap does not pass function calls/property lookups to the wrapped widget
vlad at demoninsight.com
Mon Feb 17 13:43:06 EST 2014
On Feb 17, 2014, at 12:36 PM, Ian Ward <ian at excess.org> wrote:
>> I find AttrMap to be a very useful design concept. But I think it falls
>> short of its intended purpose because I find myself constantly needing to
>> insert .base_widget in various places -- AttrMap is not as transparent as the
>> docs suggest. A piece of urwid code will work but later break when you add
>> an extra AttrMap layer somewhere. Note that using AttrWrap instead makes the
>> example above work -- even though AttrWrap is deprecated in favor of AttrMap.
>> Several stock urwid examples also break if AttrWrap is replaced with
>> AttrMap, and it originally took me a few hours to figure out why.
> I agree that it should be easier to work with decoration widgets. The
> best idea I have at the moment is to add a 'decorations' property or
> 'get_decorations()' method to widgets that will be called whenever
> that widget is added to a container or assigned to loop.widget. This
> way a widget class can define all its own decorations but still
> present just its methods and attributes to the programmer.
> How does that approach sound to you?
I think it is necessary to make a design choice: are decorations Widgets in their own right or not (i.e. some properties of Widgets instead). The frame of mind I had been in when developing my app (which is where I think the docs lead you) was something like “AttrMap is a Widget that extends the Widget that it wraps, but it ‘overrides’ that Widget’s attribute mapping). An "abbreviated" Composite pattern where AttrMap is a Widget and contains (just one, hence “abbreviated") another Widget. From this point of view, AttrMappingWidget or DecoratedWidget may be better names and it feels natural for it to “extend" the widget that it also wraps — then passing all methods and properties through makes sense and “.base_widget” is like “.super”.
An alternative design could be: don’t attempt to use a Composite pattern for decorations at all, because it would be a parallel Composite to the one that many are already used to: container Widgets. I.e. Columns is WidgetContainerMixin and it totally makes sense given how events like key strokes and mouse clicks are propagated. So, in this design choice we have a Decoration that is not a Widget and a Widget can contain multiple Decorations. But that raises more questions: if a Widget aggregates one or many Decorations, in which order are they applied? I am pretty sure that all possible answers here will only lead to a more confusing framework.
So, FWIW, I think it is best for a Decoration to accept a Widget as a constructor argument (i.e. to “wrap” it) but at the same time to be a Widget itself (which implies forwarding all methods/properties to the Widget being decorated by default). I.e. make the former choice above. I am not sure if this is the most pythonic choice, but it has been done with success before, see http://www.onjava.com/pub/a/onjava/2003/02/05/decorator.html The end users have two choices for extending and tweaking Widgets: by subclassing them or by wrapping them into Decorations (or Decorators). The former is a choice that affects all instances of the derived class and the latter decorates a single instance, so they are complementary.
As you can guess by now, I prefer the first choice. When you ask "If we pass through attribute access for just one decoration, why not do it for all decorations? If we do it for all decorations then users will be able to mostly pretend the decorations aren't there, but that could lead to some really hard to understand code” I answer “Yes” and “It’s a good thing that decorations can be transparent — it is compatible with them being Widgets”. When I write ChangeBackgroundColorDecorator(w) I find it natural not to know whether ‘w’ is a “primal” Widget or has already been decorated — it shouldn’t matter. The order of wrapping defines the order of decoration, so that’s all very natural, too. And this “pretending that the decorations aren’t there” is actually a good thing, as it will cause less coupling between various classes and more concern separation. Right now, every ‘.base_widget’ feels like a speedbump...
More information about the Urwid