[docs] [issue10043] UnboundLocalError with local variable set by setattr, caused by code run later

Cherniavsky Beni report at bugs.python.org
Sat Nov 20 23:41:17 CET 2010


Cherniavsky Beni <cben at users.sf.net> added the comment:

Hi Steven.
Please confirm if we can mark the bug closed; if you need farther advice, posting your full code (not just the error case) on comp.lang.python or StackOverflow would be more effective.

The documentation is indeed correct but hard to find (you're not the first to be surprised by UnboundLocalError); I'm working on making things more discoverable in issue 4246.
See also http://docs.python.org/py3k/faq/programming.html#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value

First of all, it's important to understand that a Python function has a *fixed* set of local variables, frozen when the function is parsed.  If you assign to it (e.g. ``name = None``), *all* appearances of the name in the function refer to a local variable; if not, they refer to the outer scope.
Therefore, you can't achieve what you want with local variables.

Generally, dynamically creating variables is a bad programming practice.  A dictionary is the cleanest way to hold a set of names/values that is not fixed.  Yes, you'll have to write ``cols['foo']`` instead of ``foo``; OTOH, setting them will not require any ugly magic...
Note also that string formatting can use values from a dictionary with very conveniently: ``"... {foo} ...".format(**cols)``.

The next best thing if ``cols['foo']`` is too verbose for you is ``cols.foo``: create an object which will contain the values as instance variables (that's a good use for setattr()).
This is the most Pythonic solution if a dictionary doesn't suffice - it's what most object-relational mappers do.

The third idea is to (ab)use a class statement.  A class statement in Python creates a temporary namespace *during the class definition* (we'll not be defining any methods or using it object-oriented stuff).
And the nice part is that you can put a class statement anywhere, even inside a function:
    def f():
        cols = {'foo': 42}  # however you fetch them...
	class temp_namespace:
            locals().update(cols)
            print(foo / 6)     # prints 7.0
        assert 'foo' not in locals()  # no effect outside the class!
This works both in CPython 2 and 3.  I'm not 100% sure that being able to change locals() in a class is guaranteed in all other implementations.  (Note again that locals() of a *function* are not a real dictionary and you *can't* change them - as I said these are fixed when the function is defined.)

The fourth idea if you must have code that says just ``foo`` to access columns is to use the exec statement - you can pass it a dictionary that will serve as globals and/or locals.  An upside is that the code will be a string and can be dynamic as well.

(BTW, if the code is not dynamic, how come you don't know the names you're accessing?  If you do, you could just set ``foo = cols['foo']`` etc. for every variable you need - tedious but no magic needed.)

Lastly, as you discovered you can dynamically create global variables.  
(As Terry said, just use the dictionary returned by ``globals()``; no need for setattr).
But this is a very last resort (changing globals for a single function is ugly), and somewhat dangerous - e.g. consider what happens if a column names changes and overwrites a function name you had...

----------
nosy: +cben

_______________________________________
Python tracker <report at bugs.python.org>
<http://bugs.python.org/issue10043>
_______________________________________


More information about the docs mailing list