Following is a function with a nested function. Please help me understand why value of 'x' is same before and after calling bar(). And why x=25 when we call the main function.
def foo():
x = 20
def bar():
global x
x = 25
print("Before calling bar: ", x)
print("Calling bar now")
bar()
print("After calling bar: ", x)
foo()
print("x in main: ", x)
This is the output we get:
Before calling bar: 20
Calling bar now
After calling bar: 20
x in main: 25
Shouldn't the code print out x=25 after calling bar() since it has x as global variable?
global is used to access global namespace outside of a local context, so in your example you access the namespace outside of foo. That's why the value of x inside foo is unchanged. If you want to access values inside foo from bar, you have to use nonlocal instead. This will result in a NameError when you try to print x on the last line of your script, since x is no longer defined in your global namespace:
def foo():
x = 20
def bar():
nonlocal x
x = 25
print("Before calling bar: ", x)
print("Calling bar now")
bar()
print("After calling bar: ", x)
foo()
print("x in main: ", x) # Raises NameError because x is not defined in global namespace
Related
When global variables are all on one script, things work smoothly.
def foo():
global x
x = 'bar'
goo()
def goo()
global x
print(x)
foo()
would print bar as expected.
However, it does not work when I have to import goo from another file, for example
file1.py
from file2 import goo
def foo():
global x
x = 'bar'
goo()
foo()
file2.py
def goo()
global x
print(x)
results in NameError. How can x be passed to the imported function like in the first case without passing it explicitly as an argument?
you have to set <module_name>.<variable_name> = 'bar' for it to work like so:
import file2
def foo():
file2.x = 'bar'
file2.goo()
foo()
file1 is the same
In the following two examples, is it correct that the variables declared by nonlocal do not exist?
Then why is there an error in the first example, while there is no error in the second example? Thanks.
Example 1:
count = 0
def make_counter():
def counter():
nonlocal count # SyntaxError: no binding for nonlocal 'count' found
count += 1
return count
return counter
Example 2:
a = 5
def f():
a=2
class C1:
a = 3
def f1(self):
nonlocal a # refers to `a` local to `f`
a = 4
def f2(self):
self.f1()
print(self.a)
print(a)
f() # 2
print(a) # 5
Per python documentation: https://docs.python.org/3/reference/simple_stmts.html#the-nonlocal-statement
nonlocal looks for the nearest enclosing scope. In Example 1, you have:
count = 0 # global scope
def make_counter():
# nonlocal scope
def counter():
# nonlocal scope, count references something in the global scope
nonlocal count
count += 1
return count
return counter
count is two "scope levels" away and is also in the global scope, so nonlocal throws a syntax error since it is not in the nonlocal scope.
In Example 2:
a = 5 # global scope
def f():
# nonlocal scope
a=2 # "a" referenced in nonlocal scope!
class C1:
# nonlocal scope
a = 3 # "a" referenced in nonlocal scope!
def f1(self):
# local scope
# "a" most recently referenced in nearest enclosing scope, no syntax error!
nonlocal a
a = 4
def f2(self):
self.f1()
print(self.a)
print(a)
f() # 2
print(a) # 5
Since a in example 2 was referenced in an adjacent nonlocal scope, it doesn't throw an error.
In this code snippet I can print the value of counter from inside the bar function
def foo():
counter = 1
def bar():
print("bar", counter)
return bar
bar = foo()
bar()
But when I try to increment counter from inside the bar function I get an UnboundLocalError error.
UnboundLocalError: local variable 'counter' referenced before assignment
Code snippet with increment statement in.
def foo():
counter = 1
def bar():
counter += 1
print("bar", counter)
return bar
bar = foo()
bar()
Do you only have read access to variables in the outer function in a Python closure?
You can't re-bind closure variables in Python 2. In Python 3, which you appear to be using due to your print(), you can declare them nonlocal:
def foo():
counter = 1
def bar():
nonlocal counter
counter += 1
print("bar", counter)
return bar
bar = foo()
bar()
Otherwise, the assignment within bar() makes the variable local, and since you haven't assigned a value to the variable in the local scope, trying to access it is an error.
In Python 2, my favorite workaround looks like this:
def foo():
class nonlocal:
counter = 1
def bar():
nonlocal.counter += 1
print("bar", nonlocal.counter)
return bar
bar = foo()
bar()
This works because mutating a mutable object doesn't require changing what the variable name points to. In this case, nonlocal is the closure variable and it is never reassigned; only its contents are changed. Other workarounds use lists or dictionaries.
Or you could use a class for the whole thing, as #naomik suggests in a comment. Define __call__() to make the instance callable.
class Foo(object):
def __init__(self, counter=1):
self.counter = counter
def __call__(self):
self.counter += 1
print("bar", self.counter)
bar = Foo()
bar()
Why can't Python increment variable in closure?
I offer a couple of solutions here.
Using a function attribute (uncommon, but works pretty well)
Using a closure with nonlocal (ideal, but Python 3 only)
Using a closure over a mutable object (idiomatic of Python 2)
Using a method on a custom object
Directly calling instance of the object by implementing __call__
Use an attribute on the function.
Set a counter attribute on your function manually after creating it:
def foo():
foo.counter += 1
return foo.counter
foo.counter = 0
And now:
>>> foo()
1
>>> foo()
2
>>> foo()
3
Or you can auto-set the function:
def foo():
if not hasattr(foo, 'counter'):
foo.counter = 0
foo.counter += 1
return foo.counter
Similarly:
>>> foo()
1
>>> foo()
2
>>> foo()
3
These approaches are simple, but uncommon, and unlikely to be quickly grokked by someone viewing your code without you present.
More common ways what you wish to accomplish is done varies depending on your version of Python.
Python 3, using a closure with nonlocal
In Python 3, you can declare nonlocal:
def foo():
counter = 0
def bar():
nonlocal counter
counter += 1
print("bar", counter)
return bar
bar = foo()
And it would increment
>>> bar()
bar 1
>>> bar()
bar 2
>>> bar()
bar 3
This is probably the most idiomatic solution for this problem. Too bad it's restricted to Python 3.
Python 2 workaround for nonlocal:
You could declare a global variable, and then increment on it, but that clutters the module namespace. So the idiomatic workaround to avoid declaring a global variable is to point to a mutable object that contains the integer on which you wish to increment, so that you're not attempting to reassign the variable name:
def foo():
counter = [0]
def bar():
counter[0] += 1
print("bar", counter)
return bar
bar = foo()
and now:
>>> bar()
('bar', [1])
>>> bar()
('bar', [2])
>>> bar()
('bar', [3])
I do think that is superior to the suggestions that involve creating classes just to hold your incrementing variable. But to be complete, let's see that.
Using a custom object
class Foo(object):
def __init__(self):
self._foo_call_count = 0
def foo(self):
self._foo_call_count += 1
print('Foo.foo', self._foo_call_count)
foo = Foo()
and now:
>>> foo.foo()
Foo.foo 1
>>> foo.foo()
Foo.foo 2
>>> foo.foo()
Foo.foo 3
or even implement __call__:
class Foo2(object):
def __init__(self):
self._foo_call_count = 0
def __call__(self):
self._foo_call_count += 1
print('Foo', self._foo_call_count)
foo = Foo2()
and now:
>>> foo()
Foo 1
>>> foo()
Foo 2
>>> foo()
Foo 3
As you can't modify immutable objects in Python, there is a workaround in Python 2.X:
Use List
def counter(start):
count = [start]
def incr():
count[0] += 1
return count[0]
return incr
a = counter(100)
print a()
b = counter(200)
print b()
here's a sample code:
def foo():
def bar():
foobar = 'foobaz'
foobar = 'foobar'
print foobar
bar()
print foobar
foo()
I want to change variable foobar inside foo by function bar. The code above will not work, since foobar inside bar is in separate namespace with foobar in foo. A simple workaround would be making a global foobar and have both foo and bar can access it, but I hope there would be simpler workarounds.
On python 3.x you can use nonlocal and for python 2.x try using function attributes:
def foo():
def bar():
foo.foobar = 'foobaz' #change the function attribute
foo.foobar = 'foobar' #declare as function attribute
print foo.foobar
bar()
print foo.foobar
foo()
output:
foobar
foobaz
You are looking for the nonlocal keyword, which exists in 3.x.
def f():
x = None
def g():
nonlocal x
x = 1
If you are stuck in 2.x, you can do it by having a list or similar mutable data container and accessing that as a work around.
def f():
x = [None]
def g():
x[0] = 1
This works as variables do fall into scope, but won't leak out of scope. With mutable objects, we can change them inside the scope, and those changes propagate out.
Not possible in python 2.7. In python 3:
def foo():
def bar():
nonlocal foobar
foobar = 'foobaz'
foobar = 'foobar'
print foobar
bar()
print foobar
foo()
In 2.x, you can do:
def foo():
foobar = []
def bar():
foobar[0] = 'foobaz'
foobar[0] = 'foobar'
print foobar[0]
bar()
print foobar[0]
foo()
def foo():
def bar():
foobar = 'foobaz'
return foobar
foobar = 'foobar'
print foobar
foobar = bar()
print foobar
foo()
Even though functions are already first class objects in Python, you can create your own "functor" or function object something like this:
class Foo(object):
def bar(self):
self.foobar = 'foobaz'
def __call__(self):
self.foobar = 'foobar'
print self.foobar
self.bar()
print self.foobar
foo = Foo()
foo()
def A():
def B():
#do something
a = A()
a.B()
Why isn't the above (such simple code) possible in Python? Is there a 'pythonic' (legible, unsurprising, non-hacky) workaround that does not turn A() into a class?
Edit 1: The above was explained to me that B is local to A, thus it only exists as long as A is being evaluated. So if we make it global (and be sure not to have it overriden), then why doesn't this work?
def A():
def B():
#do something
return A()
a = A()
a.B()
It says it's returning a 'NoneType' object.
Because a function definition just creates a name in the local namespace. What you are doing is no different than:
def f():
a = 2
and then asking why you can't access a from outside the function. Names bound inside a function are local to the function.
In addition, your proposed code is strange. when you do a = f(), you are setting a to the return value of the function. Your function returns nothing, so you can't hope to access anything through the return value. It is possible to return the inner function directly:
def f():
def g():
return "blah"
return g
>>> func = f()
>>> func()
'blah'
And this can indeed be useful. But there isn't a generic way to access things inside the function from outside except by modifying global variables (which is usually a bad idea) or returning the values. That's how functions work: they take inputs and return outputs; they don't make their innards available to the outside word.
To call B with the syntax you want, use:
def A():
def B():
print("I'm B")
A.B = B
return A
a = A()
a.B()
A.B()