Reused variable in Mako template cause “UnboundLoc

2019-07-27 06:42发布

问题:

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.

回答1:

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.



回答2:

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


标签: python mako