I am confused by the scope of the outer function variable with respect to the inner function in Python.
It seems that if a variable in the outer function is a list or array, it can be changed by the inner function while it can not if it is a scalar. For example, the following code gives an output of c = [0,-10].
def foo1():
a = [0,1]
def foo2():
a[1] = -10
foo2()
return a
c = foo1()
Whereas the following code gives c = 1.
def foo1():
a = 1
def foo2():
a = -10
foo2()
return a
c = foo1()
Why should there be a difference with respect to the type of the variable? Does this have to do with pointers?
It doesnt have anything to do with pointers, its how lists are used, when you do list[1] = x, this is a member assignment, which is actually a “method call”. Basically when ur changing the value of a member in a list, its doing a method call, that can find the name of the list in the global name space.
In the first example, "a" is passed in as a parameter, therefore you are able to access or change it as normal. In the second example, any changes to "a" stay saved only within the inner function. To fix this you can make "a" global:
def foo1():
a = 1
def foo2():
global a
a = -10
foo2()
return a
c = foo1()
Related
Python
Can anyone help me to understand this code, I am new to Python, how does this function work?
def makeInc(x):
def inc(y):
return y + x
return inc
incOne = makeInc(1)
incFive = makeInc(5)
print(incOne(5)) # returns 6
print(incFive(5)) # returns 10
Higher-order functions
Functions like makeInc that in turn, return another function are called higher order functions. Usually, functions are known to accept data as input and return data as output. With higher order functions, functions instead of data, either return code as output or accept code as input. This code is wrapped into a function. In Python, functions are first class citizens which means functions, just like data, can be passed around. For instance:
myvariable = print
Notice, how I have assigned print to myvariable and how I have dropped the parentheses after print Functions without parentheses are called function objects. This means myvariable now is just another name for print:
print("Hello World!")
myvariable("Hello World!")
Both of the above statements do the exact same thing. What can be assigned to variables can also be returned from functions:
def myfunction():
return print
myfunction()("Hello World!");
Now let's look at your example:
def makeInc(x):
def inc(y):
return y + x
return inc
makeInc is a function that accepts a parameter called x. It then defines another nested inner function called inc which takes in a parameter called y. The thing about nested functions is that they have access to the variables of the enclosing function as well. Here, inc is the inner function but it has access to x which is a variable of the enclosing outer scope.
The last statement return inc returns the inner function to the caller of makeInc. What makeInc essentially is doing, is creating a custom function based on the parameter it receives.
For instance:
x = makeInc(10)
makeInc will first accept 10 and then return a function that takes in an argument y and it increments y by 10.
Here, x is a function that takes in any argument y and then increments it by 10:
x(42) # Returns 52
nonlocal
However, there is a caveat when using nested functions:
def outer():
x = 10
def inner():
x = 20
inner()
print(x) # prints 10
Here, you would assume that the last print statement will print 20. But no! When you assign x = 20 in the inner function, it creates a new local variable called x which is initialized to 20. The outer x remains untouched. To modify the outer x, use the nonlocal keyword:
def outer():
x = 10
def inner():
nonlocal x = 20
inner()
print(x) # prints 20
If you are directly reading x inside inner() instead of assigning to it, you do not need nonlocal.
What is happening here is that makeInc() returns a function handle pointing to specific implementation of inc(). So, calling makeInc(5) "replaces" the x in inc(y) to 5 and returns the callable handle of that function. This handle is saved in incFive. You can now call the function as defined (inc(y)). Since you set x=5 before, the result will be y+5.
In Python, is there a difference between the following two codes both of which gives the same result c = [0,-10]?
def foo1():
a = [0,1]
def foo2():
a[1] = -10
foo2()
return a
c = foo1()
and
def foo1():
a = [0,1]
def foo2(b):
b[1] = -10
foo2(a)
return a
c = foo1()
Some commentator suggests this is answered by this question. But it does not since my question asks about the passing of variables through an inner function whilst the linked question does not.
In the first, a is a free variable whose value is taken from the nearest enclosing scope (in this case, the scope of foo1) that defines a.
In the second, b is a local variable initialized using the argument passed to foo2 when it is called, which is the variable a defined in foo1.
In each case, you assign -10 to the second "slot" of the same list.
class player(object):
def __init__(self, a, b):
self.a = a
self.b = b
def foo():
x += 2
obj.a += 10
obj = player(0,0)
x = 4
foo()
I understand that my foo function is not able to assign a value to the local variable x since it was previously defined globally outside the foo scope. Yet the same problem won't occur for a variable a which is an attribute of the instance obj from the player class, which in turn has nothing to do with the function foo().
Apart from the error with x += 2, I would expect an error in obj.a, i.e., I think foo() should not be able to modify obj.a, but it does! It should have thrown the same error as for x += 2 perhaps, no?
All augmented assignments effectively desugar to one of three method calls, but only in one case is an actual assignment performed.
If the lefthand side is a name, then x += y desugars to x = x.__iadd__(y). Because this is an ordinary assignment statement, the parser makes x a local variable (unless preceded by a global x or nonlocal x). Regardless of scope, x must already have a value so that the righthand side can be evaluated. In other words, x is not a free variable.
If the lefthand side is an indexed name, then x[i] += y desugars to x.__setitem__(i, x[i] + y). If x is not already defined as a local variable, it is a free variable, and so will resolve to the value
of x in the first enclosing scope.
If the lefthand side is an attribute lookup, then x.a = y desugars to
x.__setattr__('a', x.a + y). Again, x is a free variable unless a local variable x already exists.
Your comparison is apples to oranges. An example which shows you the behavior you're expecting would be:
class player(object):
def __init__(self, a, b):
self.a = a
self.b = b
def __add__(self, value):
self.a += value
def foo():
obj += 10
obj = player(0,0)
x = 4
foo()
Python permits you to read global variables inside of foo, but not redefine them. The statement obj.a += 10 only reads the variable of obj from the global scope (even though it sets a value on the instance of obj) so it is allowed. x += 2 attempts to redefine the value of x which is a global, so it is not allowed. This foo will work because we are only reading x but not redefining it:
def foo():
obj.a += 10
y = x + 10
Actually, you can't call x += 2 because x is not yet defined in your code. You can read x as a global var, or you can change it's value using global.
If you run your code, you'll get the error:
UnboundLocalError: local variable 'x' referenced before assignment
What it is saying to you is: I know there is a global x variable in your code, but you can't change it here. But, if you wish, you can create a local variable here named x, and then change it's value. Or, if you really want to modify the value from the global variable, use the global keyword, so I'll know exactly what you want to do.
Declaring it locally:
def foo():
x = 0
x += 2
obj.a += 10
obj = player(0,0)
x = 4
foo()
With global:
def foo():
global x
x += 2
obj.a += 10
obj = player(0,0)
x = 4
foo()
Now, why x.a += 10 works?
Because you are not changing the x object at all.
As mentioned by John in his answer, the same error would occur if you were changing the object itself, not its properties.
if you try to run the code below:
def foo():
obj += 10
obj = player(0,0)
x = 4
foo()
You'll get the same error as before:
UnboundLocalError: local variable 'obj' referenced before assignment
a is an attribute of player instance while x is a variable or simply value. When you are modifying x in the function, it is in local scope and results in NameError. But obj is an object in global scope and when you modify a, you are simply updating its attribute, you won't get any error here.
Without using one of the keywords global or nonlocal, a function can read but not assign a new value to a variable outside of the function's own local scope. Let's consider these two cases:
For x += 2, this is an attempt to assign a new value to the global variable x, so it is not allowed.
For obj.a += 10, it is the variable obj which is global; obj.a is an object attribute, not a "variable" in this sense. The way obj.a += 10 is evaluated is something like this: first look up the object referenced by the variable obj, and then add 10 to the attribute named a belonging to that object. Since this only reads the global variable obj and does not attempt to assign a new value to obj, it is allowed.
I want to loop over some global variable i and each time compile a new function that uses the instantaneous value of i. I can then use each of these functions in future, independent of the current value of i.
The problem is I can't find a good way to make the global variable i stay local within the namespace of the function. For example:
i = 0
def f():
return i
i = 1
print(f()) #Prints 1, when I want 0
I found one solution but it seems very clumsy:
i = 0
def g(x):
def h():
return x
return h
f = g(i)
i = 1
print(f()) #Prints 0 as I wanted.
Is there a better way to do this, maybe some kind of decorator?
Python functions are objects, you can set an attribute on them:
i = 0
def f():
return f.i
f.i = i
i = 1
print(f()) # Prints 0 as you wanted
or:
for i in range(5):
f.i = i
print(f())
Note that an argument would do the same.
When I run the following program it prints "2" and then "1" when i want it to print "2" and then "2". What have i done wrong?
def thingy(a):
a= a + 1
print(a)
return a
a=1
thingy(a)
print(a)
Many thanks if you can help.
You haven't re-assigned the a variable in your global scope. The a variable in the global scope isn't the same as in the function. To be more clear, look at this:
def thingy(arg):
arg = arg + 1
print(arg)
return arg
a=1 # Assign a to 1
thingy(a) # Do some work without altering variable
print(a) # Print a, which is 1...
a = thingy(a) # Here, a will be equals to two now.
With global option
You can also edit the global scope by adding global a at the beginning of the function and don't pass any argument to the function:
def thingy():
global a
a= a + 1
print(a)
return a
a=1
thingy()
print(a)
But prefer the first method, more cleaner.
You have remember that variables in functions are only in scope inside the function. So when you create this function:
def thingy(a):
a= a + 1
print(a)
return a
a is only in scope inside the function. No changes made to it exist outside the function. To use the returned value of a function you need to set a variable to be equal to the result of the function. So the a in the function and the a outside the function are not the same variable. Example:
def foo(a):
a = 2
return a
a = 1 #set a = 1
foo(a) #use a in function
a will still be one since the a inside the function is now out of scope.
a = 1
a = thingy(a)
print(a)
although it may look like you're setting a to 2 in the method, the scope is completely different. Imagine if you'd named the variable in the method b instead. They're different variables with the same name because of the scope.
Being a number your 'a' variable that you pass to thingy function is immutable and thus all manipulations inside your functions will not be passed back to original a. Though if you pass a as a list and change it in the function then you'll see all the changes you've made to the list after returning from a function.
def thingy(a):
a[0]= a[0] + 1
print(a[0])
a=[0]
thingy(a)
print(a[0])
you are mistaken with the a's.
functions live in their own world. they use vars of themselves. unless you tell them to use a global var.
you have lots of options to fix this:
use "global" to tell the function to use the global a.
def thingy(a):
global a;
a= a + 1
print(a)
return a
just by adding the line "global a" you tell the function "hey, you are not alone - use everyone's a".
the problem is that in your script, you use a as a global variable, and as the argument the function take. instead, write:
def thingy():
because you don't need the argument anymore.
so your code will look like this:
def thingy():
global a;
a= a + 1
print(a) #You don't need the "return a", you don't use it.
a=1
thingy()
print(a)
Use the return.
instead of using a global var, you can set "a" to whatever the function returns.
so instead of just calling thingy(a), use:
a = thingy(a)
I will use variable called "b" inside the function.
def thingy(b):
b = b + 1
print(b)
return b
a = 1
a = thingy(a)
print(a)