Hi!
On Thu, Apr 10, 2008 at 03:22:33PM +0200, Martin J. Laubach wrote:
> I just stumbled over something that utterly baffles me and hope
> someone can point out the absolutely obvious to me.

Advertising

Sure.
> I've got a view defined in a configure.zcml that points to this class:
>
> class MyView(BrowserView):
> def __call__(self):
> self.pt = ViewPageTemplateFile('empty.pt')
> data = self.pt()
> return data
>
> That works fine.
I'm actually surprised that this works.
The usual way in Zope 3 is
class MyView(BrowserView):
pt = ViewPageTemplateFile('empty.pt')
def __call__(self):
data = self.pt()
return data
which can also be shortened to
class MyView(BrowserView):
__call__ = ViewPageTemplateFile('empty.pt')
> However, I don't really need pt as instance variable,
> so I turned it into a local variable:
>
> class MyView(BrowserView):
> def __call__(self):
> pt = ViewPageTemplateFile('empty.pt')
> data = pt()
> return data
>
> And that version throws an exception:
>
> Traceback (innermost last):
> Module ZPublisher.Publish, line 119, in publish
> Module ZPublisher.mapply, line 88, in mapply
> Module ZPublisher.Publish, line 42, in call_object
> Module mjl.example.browser.myform, line 31, in __call__
> Module Shared.DC.Scripts.Bindings, line 313, in __call__
> Module Shared.DC.Scripts.Bindings, line 348, in _bindAndExec
> Module Shared.DC.Scripts.Bindings, line 1, in ?
> Module Shared.DC.Scripts.Bindings, line 293, in _getTraverseSubpath
This does not look like Zope 3 to me.
> AttributeError: 'str' object has no attribute 'other'
Okay, I know why the code doesn't work, but I don't know why you get
this strange Zope 2 error. I'll answer just the first part.
>
> Same if I just do the simpler "return ViewPageTemplateFile('empty.pt')()"
> of course.
>
> I simply do not understand why, what or who does care how I name my
> variables or where I put them? Please hit me hard with a cluebat. Twice.
You have encountered the magic of Python descriptors. Feel free to skip
the detailed explanation if you're in a hurry:
ViewPageTemplateFile is a descriptor, which is a fancy way of saying
"object that has a method named __get__". When you access a descriptor
from an instance, Python silently calls __get__ for you, so
class MyView(BrowserView):
pt = ViewPageTemplateFile('empty.pt')
def __call__(self):
pt = self.pt()
is more or less equivalent to
class MyView(BrowserView):
def __call__(self):
pt = ViewPageTemplateFile('empty.pt')
pt = pt.__get__(self, ViewPageTemplateFile)
ViewPageTemplateFile.__get__ returns a BoundPageTemplate object
(it's a bit like the difference between unbound and bound methods)
that knows which view it is bound to and can pass it via the "view"
name to your TALES expressions.
Now when you invoke ViewPageTemplateFile's __call__, you have to
pass the view as the first argument:
class MyView(BrowserView):
def __call__(self):
pt = ViewPageTemplateFile('empty.pt')
return pt(self)
but when you invoke BoundPageTemplate.__call__, it knows and
provides the view argument itself:
class MyView(BrowserView):
def __call__(self):
pt = ViewPageTemplateFile('empty.pt')
bound_pt = pt.__get__(self, ViewPageTemplateFile)
return bound_pt()
which is more or less the same as
class MyView(BrowserView):
pt = ViewPageTemplateFile('empty.pt')
def __call__(self):
bound_pt = self.pt
return bound_pt()
[End of explanation]
So, if you really want to use a local variable instead of a class
attribute, pass the view itself as the first argument to __call__
class MyView(BrowserView):
def __call__(self):
pt = ViewPageTemplateFile('empty.pt')
data = pt(self)
return data
HTH,
Marius Gedminas
--
Anyone can do any amount of work provided it isn't the work he is supposed
to be doing at the moment.
-- Robert Benchley