Skip to content Skip to sidebar Skip to footer

Why Commented Assignment Statement In The "if False:" Block Cause Difference "NameError" Message?

If I commented the baz = 4 statement in if False: block I get the message of NameError: name 'baz' is not defined else I get the message of NameError: free variable 'baz' reference

Solution 1:

The variable baz is never assigned, but according to the code, it existsts in the local namespace. So there is an entry reserved for it, thus it isn't "not defined", but it is "not assigned".

Let's extend your example:

def foo(b):
    def bar(): return baz
    if b: baz = 4
    return bar, locals()

and use it:

>>> a, b = foo(0)
>>> c, d = foo(1)
>>> a
<function foo.<locals>.bar at 0x000002320BA6A0D0>
>>> b
{'bar': <function foo.<locals>.bar at 0x000002320BA6A0D0>, 'b': 0}
>>> c
<function foo.<locals>.bar at 0x000002320BA6A268>
>>> d
{'bar': <function foo.<locals>.bar at 0x000002320BA6A268>, 'b': 1, 'baz': 4}
>>> a.__closure__
(<cell at 0x000002320BA3E9A8: empty>,)
>>> c.__closure__
(<cell at 0x000002320BA3E948: int object at 0x000000006E2C6140>,)
>>> a.__closure__[0].cell_contents
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: Cell is empty
>>> c.__closure__[0].cell_contents
4
>>> foo.__code__.co_cellvars
('baz',)

Here we see the difference: in the first case, the cell in the closure remains empty, in the second case it contains an int object (which is 4).


Solution 2:

During the compilation process , python will note down the variables that are been used by the program irrespective of the condition . So under your case

if False:
    baz = 4
    pass

Here, baz will be caught by the compiler.

Compiler : "Hey i know the list of variables you use." So when you try to call for bar() [which uses the baz variable]. Compiler will search for the baz assignment . It cant found because your "if" block will never execute . So it throws out

NameError: free variable 'baz' referenced before assignment in enclosing s

Ref: Geeks For Geeks : Python Closure

Thanks


Solution 3:

In short

@glglgl got the crucial point

there is an entry reserved for it [baz], thus it isn't "not defined", but it is "not assigned".

More details ?

From your question, you seem familiar with the concept of closures, and we'll not get further into "why does it raise an error", but only on "why do we get two different errors?"

During the bytecode compilation, python will build a symbol table, that is essentially a list of all scopes, and the names that are defined within. It is used a runtime for variable resolution. When doing so, python does not care about conditions: it is just building a list of all identifiers the code has, and finding in which scope they will be stored.

This symbol table is thus :

  • taking into account lines of code
  • ...event unreachable lines
  • but not (obviously) comments

a bit of python practice

Well, you might say, can you prove what you said ? Here's a snippet :)

definition in unreachable statement

  • in foo the table sees baz as a local variable, and that there is a code path that assigns a value to it (it doesn't care about the fact that it is unreachable)
  • in bar, baz is a non local variable, and some code references it
import symtable

code = """
def foo():
  def bar():
    return baz + 1
  if False:
    baz = 4
    pass
  return bar()
"""

table = symtable.symtable(code, '<string>', 'exec')

foo_namespace = table.lookup('foo').get_namespace()
bar_namespace = foo_namespace.lookup('bar').get_namespace()
baz_in_foo = foo_namespace.lookup('baz')
baz_in_bar = bar_namespace.lookup('baz')

print("With unreachable statement 'baz=4'")
print('is_referenced, is_assigned, is_local')
print(baz_in_foo, baz_in_foo.is_referenced(), baz_in_foo.is_assigned(), baz_in_foo.is_local())
print(baz_in_bar, baz_in_bar.is_referenced(), baz_in_bar.is_assigned(), baz_in_bar.is_local())
# With unreachable statement 'baz=4'
#                is_referenced, is_assigned, is_local
# baz_in_foo     False          True         True
# baz_in_bar     True           False        False

no definition at all

  • in foo, baz does not exists in the table
  • in bar, baz is a non-local variable (but python does not know in which scope it can find it)
code = """
import symtable

def foo():
  def bar():
    return baz + 1
  if False:
    # baz = 4
    pass
  return bar()
"""

exec(code)
table = symtable.symtable(code, '<string>', 'exec')

foo_namespace = table.lookup('foo').get_namespace()
bar_namespace = foo_namespace.lookup('bar').get_namespace()
# baz_in_foo = foo_namespace.lookup('baz') # raise KeyError
baz_in_bar = bar_namespace.lookup('baz')

print("Without unreachable statement 'baz=4'")
print('is_referenced, is_assigned, is_local')
print('baz_in_bar', baz_in_bar.is_referenced(), baz_in_bar.is_assigned(), baz_in_bar.is_local())
# Without unreachable statement 'baz=4'
#                is_referenced, is_assigned, is_local
# baz_in_bar    True           False        False

Sources

https://eli.thegreenplace.net/2011/05/15/understanding-unboundlocalerror-in-python


Post a Comment for "Why Commented Assignment Statement In The "if False:" Block Cause Difference "NameError" Message?"