I have this "funny" issue. I know this error message is found in a lot of places, but I couldn't find one explicitely related to Mako.
In a Mako template, I have (snippet):
<%inherit file="other.mako"/>
<%def name="my_method()">Search for ${label}</%def>
[...]
<h2>${label.capitalize()} found: ${len(l)}</h2>
...
<ol>
% for (label, field_name) in some_list:
<li>${label}: ${field_name}</li>
% endfor
</ol>
and I would get the error:
UnboundLocalError: local variable 'label' referenced before assignment
The weird part is that if I just don't use the second ${label.capitalize()}
, I don't get any error, and the value of ${label}
in the <%def>
is the one I want, not the one from the for-loop. If I had the same error with the variable in <%def>
, it may be clear to not reuse the same variable name, but in this case, I'm quite puzzled that such thing happens.
Can anyone tell me how I can avoid this beside renaming the variable label in the for-loop? If I rename the for-loop variable name, the problem disappear. I am transferring from another templating system that did not have this sort of error, so this similar scenario happens quite often.
Thanks for any pointers,
D.
EDIT for clarity:
I am calling my template using:
renderers.render_to_response(my_mako_tmpl,
{'label': 'value', 'somelist':[a,b,c], 'l':[]},
request=request)
My question is: Why the fact that I have the % for (label, field_name)
-loop, the variable label
is giving me and error in ${label.capitalize()}
, where as it does not give me any error for Search for ${label}
.
If I change my for-loop for:
% for (label_name, field_name) in some_list:
I get no error.
If I don't change the for-loop, but I change:
<h2>${label.capitalize()} found: ${len(l)}</h2>
to
<h2>Items found: ${len(l)}</h2>
I get no error even if ${label}
is used in my <%def>
.
Also, to add info for the template usage, I added the <%inherit>
and here is how the other.mako
is defined (snippet):
<%def name="my_method()">Default value</%def>
Value of my_method() = ${self.my_method()}
So I don't need to pass any value to my_method()
.
Hope this make the question more clear.
It is best to think of a Mako template as being similar to a piece of python code. Thus, as in python, you will have issues if you expect a variable to take on both local scope and global (or nested scope).
Thinking of a template as a function that takes variables, and defines within it various other functions including a body()
function, then your problem is effectively similar to the following
>>> def f(x):
... def body():
... print x
... for x in range(5):
... print x
... body()
...
>>> f(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in f
File "<stdin>", line 3, in body
UnboundLocalError: local variable 'x' referenced before assignment
You shouldn't expect this to work in Python, and similarly, you shouldn't expect it to work in a Mako template.
I think you need to include label in my_method arguments.
<%def name="my_method(label)">Search for ${label}</%def>
[...]
<h2>${label.capitalize()} found: ${len(l)}</h2>
...
<ol>
% for (label, field_name) in some_list:
<li>${label}: ${field_name}</li>
% endfor
</ol>
Follow-up
As far as I can tell you'll have to change the loop variable name. It may be a nuance of Mako or Python namespaces that I don't completely understand, but this appears to be the safest option. In my opinion reusing variable names like this is bad form anyway and leads to unexpected behaviour, is error prone, etc.
It appears from my evaluation that this may be a feature of mako in the way it handles namespaces. Consider the following examples. The first raises the same UnboundLocalError exception. The second is identical in structure and does not raise exception.
Example 1 raises UnboundLocalError:
from mako.template import Template
src = """
Text in "label": ${label}
Elements of "some_list": ${some_list}
Labels and names in "some_list"
% for (label,name) in some_list:
label: name -> ${label}: ${name}
% endfor
${label.capitalize()}
"""
my_template = Template(src)
s = [('foo', 'bar'), ('spam', 'eggs')]
data = {'label': 'bogus', 'some_list':s, 'l':[1, 2, 3, 4]}
print(my_template.render(**data))
Outputs:
Traceback (most recent call last):
...
File "C:\Python26\ArcGIS10.0\Lib\site-packages\mako-0.6.2-py2.6.egg\mako\runtime.py", line 704, in _exec_template
callable_(context, *args, **kwargs)
File "memory:0xb5b6b0", line 22, in render_body
UnboundLocalError: local variable 'label' referenced before assignment
Example 2 evaluates successfully:
from mako.template import Template
src = """
Text in "label": ${label}
Elements of "some_list": ${some_list}
Labels and names in "some_list"
% for (loop_label,name) in some_list:
label: name -> ${loop_label}: ${name}
% endfor
${label.capitalize()}
"""
my_template = Template(src)
s = [('foo', 'bar'), ('spam', 'eggs')]
data = {'label': 'bogus', 'some_list':s, 'l':[1, 2, 3, 4]}
print(my_template.render(**data))
Output:
Text in "label": eggs
Elements of "some_list": [('foo', 'bar'), ('spam', 'eggs')]
Labels and names in "some_list"
label: name -> foo: bar
label: name -> spam: eggs
Eggs
To demonstrate that Mako is not evaluating the template as you might expect, here is a similarly structured pure Python example that evaluates just fine.
src = """
print('Text in "label": %s' % label)
print('Elements of "some_list": %s' % some_list)
print('')
print('Labels and names in "some_list"')
for (label,name) in some_list:
print(' label: name -> %s:%s' % (label, name))
print('')
print('Caps "label": %s' % label.capitalize())
"""
code = compile(src, 'None', 'exec')
s = [('foo', 'bar'), ('spam', 'eggs')]
data = {'label': 'bogus', 'some_list':s, 'l':[1, 2, 3, 4]}
eval(code, {}, data)
Outputs:
Text in "label": bogus
Elements of "some_list": [('foo', 'bar'), ('spam', 'eggs')]
Labels and names in "some_list"
label: name -> foo:bar
label: name -> spam:eggs
Caps "label": Spam