Python's closure - local variable referenced before assignment - python

I am trying to understand how closure works in Python.
I feel like add1 should work just fine here. I would expect the variable x to have been defined when helper is called. However, it is giving me a Local variable referenced before assignment error.
add2 is very similar to add1. Instead of assigning x with an integer, it assigns it with a dictionary. The behavior of it is also aligned with what I would expect. x is defined and reference-able inside helper.
import random
def add1():
x = 0
def helper():
x = x + 1
return x
return helper
def add2():
x = {}
def helper():
x[random.randint(1,1000)] = 3
return x
return helper
if __name__ == '__main__':
a1 = add1()
a2 = add2()
# print(a1()) #This causes error
print(a2()) #{650: 3}
print(a2()) #{650: 3, 333: 3}
What's the logic behind this? What am I doing differently other than that the types of x are different?

You're expecting the compiler to know that the variable has been bound outside the closure. This is not true, and hence you need to use nonlocal to indicate this.
def add1():
x = 0
def helper():
nonlocal x
x = x + 1
return x
return helper
EDIT By denniss:
nonlocal is not necessary in add2 because it's just modifying x and not re-binding it (aka not re-assigning it). Whereas in add1, x= x+1 is a re-assignment.

Related

Are functions and helper functions treated differently in python?

Over here, the main function is g(x) and the helper function is h(). I noticed I can get the output for g(3), simply by binding x=3 and then doing any of the three
print(g(x))
g(x)
z=g(x)
But on the other hand, I noticed h() is outputted only when I type "print(h())". Are my observations correct or did I make a mistake? And also what is the logic behind this weird discrimination?
I like to think of it this way. Usually, if you had a line like "5" or "x=5", python doesn't give an output of 5. But functions have been given a special feature where they are invoked in any of the 3 ways. It's only that this special feature is being 'withdrawn' in the case of helper functions
The code you have written is equivalent to this:
def g(x):
def h():
any_name_you_want = 'abc'
return any_name_you_want
x += 1
print("in g(x)", x)
print(h())
return x
You cannot assign to a non-local variable inside a function. When Python creates the namespace for h, x is local to h because the assignment requires python to add the namespace for x at runtime.
Hence your code is also equivalent to :
def g(x):
def h():
return 'abc'
x += 1
print("in g(x)", x)
print(h())
return x
To get a sense of what is happening, run the following and then read up on UnboundLocalError:
def g(x):
def h():
x = x
return x
x += 1
print("in g(x)", x)
print(h())
return x

Python function argument can't be used as a variable in a child function

Here's the problem I try to solve:
I have a first function, to which I put in arguments. Then, later on, I have a second function, from which I want to call, as a variable, the said argument of the parent function. So it goes like:
def parent_function(argument=x):
if statement:
child_function()
else:
...
return result
def child_function():
x = x + 5
return x
If I run such a code, I get an error in the child function saying name 'x' is not defined.
However, if I fix my code to make x global in the parent function, like this:
def parent_function(argument=x):
global x
if statement:
child_function()
else:
...
return result
def child_function():
x = x + 5
return x
I get the error name 'x' is parameter and global
I need to import both functions in another file and I can't "dismantle" the child function inside the parent function.
Thanks very much for any help !
Don't use global Variables. Every function needs it's own arguments:
def parent_function(x):
if statement:
x = child_function(x)
else:
...
return result
def child_function(x):
x = x + 5
return x
name 'x' is parameter and global means you can't overwrite parameter x for being global also. To fix this, use another variable y, like this:
def parent_function(argument=x):
global y
y = x
if statement:
child_function()
else:
...
return result
def child_function():
y = y + 5
return y
This error happens because you are trying to overwrite a parameter in a function whose scope is local to that function by giving it a global scope. The problem is that variables defined in the context of a function are by definition local variables. To better illustrate this problem ,you can simply try to launch this piece of code:
def parent_function(argument="Hello"):
global argument
return argument
You will see that it will fail to run for the same reason that I have explained. I hope I have been clear in my explanation. Good luck.
The first thing you need to change is this:
def parent_function(argument=x):
If you search for how to make a default argument in a function you will get something like this: https://www.geeksforgeeks.org/default-arguments-in-python/
. This means instead of x you need to have some default value, for example:
def parent_function(argument=5):
This means that if you do not pass the argument called argument to the function value 5 will be passed.
On the other hand, it seems that you want x to be an argument, which means the def line should look like this:
def parent_function(x=5):
Second, global keyword needs to be used in the child_function since x has not been used in parent_function. This leads to this:
def parent_function(x=5):
if statement:
child_function()
else:
...
return result
def child_function():
global x
x = x + 5
return x
To have all this work, there must be at least two more lines one to set x and another to call parent_function, like this:
x = 6
parent_function(4)
But, to be even funnier, x from the arguments in parent_function and x used in child_function are not the same thing, and you can see for yourself in this example which is similar to your code, but fully executable:
def parent_function(x=5):
if True:
print(child_function())
else:
print("else branch")
return True
def child_function():
global x
x = x + 5
return x
x = 6
parent_function(4)
This prints out 11 even you might think it will print out 9!
This is due to fact that keyword global refers to the (as the word says) global variable declared outside of the functions, the variable with value 6. Usually, local and global variables should have different names, so either the argument x in parent_function or global x variable needs to be renamed.
IDK if this helps, but you will learn something from this, for sure!

Closure and variable scope in python 3.x issue causing me confusion

I am using Python 3.7.0 and I am doing some experiments in order to comprehend the subtleties of variable scopes but I still don't understand this behaviour.
When I execute this code:
def f():
x=0
def g():y=x+1;x=y
return g
f()()
I get UnboundLocalError: local variable 'x' referenced before assignment
However when I execute this one:
def f():
x=0
def g():y=x+1
return g
f()()
It works fine. And even this one:
def f():
x=0
def g():x=1
return g
f()()
It also works fine. So I am confused. It seems to me that if assigning the value 1 to the nonlocal variable x in the g function works fine alone and on the other hand if assigning an expression containing x to a local variable y in the function g works also fine then the instructions y=x+1 and x=y should both work. I don't understand what is causing the error. I feel I am missing something fundamental in my understanding of Python's behaviour.
I'll take an ill-advised crack at this. In the following code:
def f():
x = 0 # assignment makes this x local to f()
def g():
y = x + 1
x = y # assignment makes this x local to g()
return g
f()()
The assignment of x inside g() forces it to be a variable local to g(). So the first line causes the error as you accessing the value of an unassigned local. I believe this "assignment makes it local" logic doesn't follow the flow of your program -- the function is viewed as whole to make this determination and then the code is looked at line by line.
You can access any variable in scope from a function, but you can only set locals. Unless you've declared the variable you're assigning as global or nonlocal:
def f():
x = 0 # assignment makes this x local to f()
def g():
nonlocal x
y = x + 1
x = y # x is declared to come from a nonlocal scope
return g
f()()
In your second example, it's not an issue as you're only looking at the value of x, not setting it, so it can come from any appropriate scope:
def f():
x = 0 # assignment makes this x local to f()
def g():
y = x + 1 # x comes from an outer scope
return g
f()()
Finally, your third example is the same as the first without the unssigned local variable usage error:
def f():
x = 0 # assignment makes this x local to f()
def g():
x = 1 # assignment makes this x local to g()
return g
f()()
It seems to me that if assigning the value 1 to the nonlocal variable
x in the g function works fine alone
How did you determine that the reassignment of x in f() worked fine?

Returning functions

I have functions that use returned variables.
How do I call them in a main function like this:
text = 'a'
def a(text):
text = x
return x
def b(x):
x = z
return z
def main():
# What do I put here to call function a and b?
main()
How do I call them
You already know how to call them ;)
def main():
...
main()
So I assume you are bewildered by:
def a(text):
text = x
return x
That x is not defined in a nor is it passed by argument to it. But that's OK. In python, all variables defined outside function are still visible inside it. Only exception is if argument of function have the same name as one of those variables. Then argument will take precedence.
This mechanism is called closure. Function close over some variable that already exists in scope when function is defined.
You can learn about it some more here:
https://www.learnpython.org/en/Closures
text = 'a'
def a(text):
text = x
return x
def b(x):
x = z
return z
def main():
x = a(text)
z = b(x)
main()
This might be what you want
It is not clear what are you trying to achieve. You are using variable x in the function a and variable z in function b without defining these two variables.
Moreover, you are setting variable text in function a without capturing it using statement global, look here for more information.
And global variables are considered to be bad practice.

Difference between local variable and global variable

I'm confused on the difference between local variables and global variables. I know that global variables are declared outside a function while local is declared in a function. However, I'm wondering if it is as so:
def func(l):
a = 0
n = len(l)
w = l[0]
while...
My question is, in the function i wrote as an example, i know that a is a local variable but what about the other two? are they local variables too?
l is a location variable that you passed into the function, and so is w since w is a "copy" of l[0]
To have a global variable you need to declare the variable as global using the global keyword.
x = 1
y = 2
def func(l):
global x
x = 4
l = 5
print("x is {0}, y is {1}".format(x,l))
func(y)
print("x is {0}, y is {1}".format(x,y))
Returns:
x is 4, y is 5
x is 4, y is 2
Notice how x is now changed but y isn't?
Note that lists are special because you don't need to declare the global keyword to append or remove from them:
x = []
def func():
x.append(3)
print("x is {0}".format(x))
func()
print("x is {0}".format(x))
Returns:
x is [3]
x is [3]
But references to lists are not global because they are a 'copy' of it :
x = [3]
y = 1
def func():
y = x[0]
print("y is {0}".format(y))
func()
print("y is {0}".format(y))
Returns:
y is 3
y is 1
Also Reassigning the variable that was a list is not global:
x = [3]
def func():
x = [2]
print("x is {0}".format(x))
func()
print("x is {0}".format(x))
Returns:
x is [2]
x is [3]
Also note that there are always a better way to do something than to declare global variables, because as your code scales it will get messier.
All those variables are assigned values and that automatically declares them in the local scope. So they are local variables.
Even if they had been declared outside in the global scope, they would still be local.
Unless you used the global keyword which tells the interpreter that the current scope refers to a previously declared global or creates a new one in the global context if none with the same name exists.
def func(l):
global n # n is declared in the global scope
a = 0
n = len(l)
w = l[0]
while...
func()
print(n)
All the variables in your function are local, usable by that function only. Global variables are usable by all functions in the class. In your function
def func(l):
a = 0
n = len(l)
w = l[0]
while...
>>>a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
a, n, w, and l are not usable outside of the func scope. If you did something like this...
a = 0
def func(l):
n = len(l)
w = l[0]
while...
>>>a
0
I just happened to come across this read which provides you with a lot of detail on variable declaration and scope.
[Python Doc the rule for local and global variables]:
In Python, variables that are only referenced inside a function are
implicitly global. If a variable is assigned a value anywhere within
the function’s body, it’s assumed to be a local unless explicitly
declared as global. illustrate this with your example:
Use your example to illustrate, all variables declared inside the func are local variables. and even values declared outside of the function. like variable x it has no parent function but it is still actually NOT a global variable. x just get commits to memory before the func gets called
x = 5 #actually local
def func(l):
a = 0
n = len(l)
w = l[0]
print(x) # x is local we call call it, use it to iterate but thats is pretty much it
if we try to modify the local variable x you would get an error:
x = 5
def func(l):
a = 0
n = len(l)
w = l[0]
x = x + 1 # can't do this because we are referning a variable before the funtion scope. and `x` is not global
UnboundLocalError: local variable 'x' referenced before assignment
but now if we define x as global then we can freely modify it as Python know anywhere when we call x is references to the same variable and thus the same memory address
x = 5
def func():
global x
x = x + 1
func()
print(x)
it prints out : 6 for the value of x
Hope through this example you see that global variable can be accessed anywhere in the program. Whereas local variable can only be accessed within its function scope. Also, remember that even though global variables can be accessed locally, it cannot be modified locally inherently.
Local Variable : When we declare a variable inside a function, it becomes a local variable.
global variable : When we declare a variable outside a function , it becomes a global variable.
A python program understand difference local vs global variable
#same name for local and global variable.
a = 1 #this is global variable
def my_function():
a = 2 #this is local variable
print("a= ", a) #display local var
my_function()
print("a = ", a) #display global var.
This program to access global variable
a = 1 #This is global var
def my_function():
global a #This is global var.
print("global a= ", a) #display new value
a = 2 #modify global var value
print("modify a = ", a) #display a new value
my_function()
print("global a= ", a) #display modified value

Categories

Resources