Difference in transmitting variables in an inner function in Python - python

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.

Related

the answer variable is not being updated in max depth of binary tree question [duplicate]

Consider this example:
def A():
b = 1
def B():
# I can access 'b' from here.
print(b)
# But can i modify 'b' here?
B()
A()
For the code in the B function, the variable b is in a non-global, enclosing (outer) scope. How can I modify b from within B? I get an UnboundLocalError if I try it directly, and using global does not fix the problem since b is not global.
Python implements lexical, not dynamic scope - like almost all modern languages. The techniques here will not allow access to the caller's variables - unless the caller also happens to be an enclosing function - because the caller is not in scope. For more on this problem, see How can I access variables from the caller, even if it isn't an enclosing scope (i.e., implement dynamic scoping)?.
On Python 3, use the nonlocal keyword:
The nonlocal statement causes the listed identifiers to refer to previously bound variables in the nearest enclosing scope excluding globals. This is important because the default behavior for binding is to search the local namespace first. The statement allows encapsulated code to rebind variables outside of the local scope besides the global (module) scope.
def foo():
a = 1
def bar():
nonlocal a
a = 2
bar()
print(a) # Output: 2
On Python 2, use a mutable object (like a list, or dict) and mutate the value instead of reassigning a variable:
def foo():
a = []
def bar():
a.append(1)
bar()
bar()
print a
foo()
Outputs:
[1, 1]
You can use an empty class to hold a temporary scope. It's like the mutable but a bit prettier.
def outer_fn():
class FnScope:
b = 5
c = 6
def inner_fn():
FnScope.b += 1
FnScope.c += FnScope.b
inner_fn()
inner_fn()
inner_fn()
This yields the following interactive output:
>>> outer_fn()
8 27
>>> fs = FnScope()
NameError: name 'FnScope' is not defined
I'm a little new to Python, but I've read a bit about this. I believe the best you're going to get is similar to the Java work-around, which is to wrap your outer variable in a list.
def A():
b = [1]
def B():
b[0] = 2
B()
print(b[0])
# The output is '2'
Edit: I guess this was probably true before Python 3. Looks like nonlocal is your answer.
No you cannot, at least in this way.
Because the "set operation" will create a new name in the current scope, which covers the outer one.
I don't know if there is an attribute of a function that gives the __dict__ of the outer space of the function when this outer space isn't the global space == the module, which is the case when the function is a nested function, in Python 3.
But in Python 2, as far as I know, there isn't such an attribute.
So the only possibilities to do what you want is:
1) using a mutable object, as said by others
2)
def A() :
b = 1
print 'b before B() ==', b
def B() :
b = 10
print 'b ==', b
return b
b = B()
print 'b after B() ==', b
A()
result
b before B() == 1
b == 10
b after B() == 10
.
Nota
The solution of Cédric Julien has a drawback:
def A() :
global b # N1
b = 1
print ' b in function B before executing C() :', b
def B() :
global b # N2
print ' b in function B before assigning b = 2 :', b
b = 2
print ' b in function B after assigning b = 2 :', b
B()
print ' b in function A , after execution of B()', b
b = 450
print 'global b , before execution of A() :', b
A()
print 'global b , after execution of A() :', b
result
global b , before execution of A() : 450
b in function B before executing B() : 1
b in function B before assigning b = 2 : 1
b in function B after assigning b = 2 : 2
b in function A , after execution of B() 2
global b , after execution of A() : 2
The global b after execution of A() has been modified and it may be not whished so
That's the case only if there is an object with identifier b in the global namespace
The short answer that will just work automagically
I created a python library for solving this specific problem. It is released under the unlisence so use it however you wish. You can install it with pip install seapie or check out the home page here https://github.com/hirsimaki-markus/SEAPIE
user#pc:home$ pip install seapie
from seapie import Seapie as seapie
def A():
b = 1
def B():
seapie(1, "b=2")
print(b)
B()
A()
outputs
2
the arguments have following meaning:
The first argument is execution scope. 0 would mean local B(), 1 means parent A() and 2 would mean grandparent <module> aka global
The second argument is a string or code object you want to execute in the given scope
You can also call it without arguments for interactive shell inside your program
The long answer
This is more complicated. Seapie works by editing the frames in call stack using CPython api. CPython is the de facto standard so most people don't have to worry about it.
The magic words you are probably most likely interesed in if you are reading this are the following:
frame = sys._getframe(1) # 1 stands for previous frame
parent_locals = frame.f_locals # true dictionary of parent locals
parent_globals = frame.f_globals # true dictionary of parent globals
exec(codeblock, parent_globals, parent_locals)
ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(frame),ctypes.c_int(1))
# the magic value 1 stands for ability to introduce new variables. 0 for update-only
The latter will force updates to pass into local scope. local scopes are however optimized differently than global scope so intoducing new objects has some problems when you try to call them directly if they are not initialized in any way. I will copy few ways to circumvent these problems from the github page
Assingn, import and define your objects beforehand
Assingn placeholder to your objects beforehand
Reassign object to itself in main program to update symbol table: x = locals()["x"]
Use exec() in main program instead of directly calling to avoid optimization. Instead of calling x do: exec("x")
If you are feeling that using exec() is not something you want to go with you can
emulate the behaviour by updating the the true local dictionary (not the one returned by locals()). I will copy an example from https://faster-cpython.readthedocs.io/mutable.html
import sys
import ctypes
def hack():
# Get the frame object of the caller
frame = sys._getframe(1)
frame.f_locals['x'] = "hack!"
# Force an update of locals array from locals dict
ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(frame),
ctypes.c_int(0))
def func():
x = 1
hack()
print(x)
func()
Output:
hack!
I don't think you should want to do this. Functions that can alter things in their enclosing context are dangerous, as that context may be written without the knowledge of the function.
You could make it explicit, either by making B a public method and C a private method in a class (the best way probably); or by using a mutable type such as a list and passing it explicitly to C:
def A():
x = [0]
def B(var):
var[0] = 1
B(x)
print x
A()
For anyone looking at this much later on a safer but heavier workaround is. Without a need to pass variables as parameters.
def outer():
a = [1]
def inner(a=a):
a[0] += 1
inner()
return a[0]
You can, but you'll have to use the global statment (not a really good solution as always when using global variables, but it works):
def A():
global b
b = 1
def B():
global b
print( b )
b = 2
B()
A()

Scope of an outer function variable?

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()

Nested Function Scope [duplicate]

This question already has answers here:
UnboundLocalError with nested function scopes [duplicate]
(3 answers)
Closed 2 years ago.
def foo():
a = 0
def bar():
a += 1
bar()
foo()
Why is it that this code does not execute when a is an immutable type, but does execute when it is mutable (a list, for example)?
I don't think the other answers narrate the entire story, so here's my 2 cents.The scope does not differ between types. That's not what's happening here. Rebinding a name, regardless of what that name refers to, will always cause an unbound local error to occur.
Some types in Python are mutable (once created, a value can be changed), and list is one of them. Some are immutable, and changing a value of these types requires creating a new object.
So, with the first example,
def foo():
a = 0
def bar():
a += 1
bar()
foo()
This doesn't work because you effectively have an assignment taking place here, a = a + 1. You can use non-local to make the above work, that being besides the point.
Doing the same thing with a list:
def foo():
a = []
def bar():
a.append(1)
bar()
foo()
This does indeed work. There is no assignment taking place here.
You can't re-bind the name to a different object, but if the object is mutable, you can modify its contents.
Now, there's 2 more cases you should be aware of.
def foo():
a = []
c = []
def bar():
a = c + [1]
bar()
print(a)
print(c)
foo()
This will work, however you should note that the a inside bar() now is local to bar, and the print() statement should reflect that.
But here's a gotcha
def foo():
a = []
def bar():
a = a + [1] #or even a += [1], doesn't matter
bar()
print(a)
foo()
This won't work! (And it's important you contrast this snippet with the first snippet, because that addresses why this has nothing to do with scopes. Take a minute to read it again.)
So this doesn't work and it's important to understand it.
If there is an assignment to a variable inside a function, that variable is considered local. Now, in the last case, when we did a = c + 1, it was more like a_local = c_nonlocal + 1.
In this case, a = a + 1 is a_local = a_local + 1 and hence, that will indeed cause an error. This is why, Rebinding a name, regardless of what that name refers to, will always cause an unbound local error to occur. In the case earlier(The third snippet), it wasn't rebinding it - it was creating a local variable. In the latter case(The fourth snippet), it was infact rebinding and hence the error.
There is a keyword nonlocal which is used to access nonlocal variables.
The nonlocal keyword is used to work with variables inside nested functions, where the variable should not belong to the inner function. --w3schools.com
def foo():
a = 0
def bar():
nonlocal a
a += 1
bar()
foo()
Thank you
That is because you are not returning any value from either functions and the second a is only local to the function bar().
Try using the nonlocal keyword like this:
def foo():
a = 0
def bar():
nonlocal a
a += 1
return a
return bar()
print(foo())
Output
1

Are lists implicitly 'global' inside of functions? [duplicate]

This question already has answers here:
Modify global list inside a function
(5 answers)
Closed 6 years ago.
I was experimenting with this code:
def a():
#global p
p.append(4);
d=9
p=[2,3];
d=8
a();
print p # ----> [2, 3, 4]
print d # ----> 8
The variable d value is not changed as I didn't use the global keyword. But the list p was modified in function even though I didn't use global. Are all lists global by default in functions?
The critical difference is the assignment here. You are fine calling methods on existing global objects, but you can't assign to them without calling them global. In your code, the name d is being reassigned to reference another value. If you changed p with assignment you'd have a similar result
def a():
p = [5, 7] # new local variable, doesn't change global
p.append(9) # doesn't change global p
This makes sense if you think about what happens when python encounters the name for the first time. In the function you've provided, python will see p.append and say "hm, I don't have a local by the name p, let me look in the enclosing scope." It sees the global p and uses that.
In the example I've shown, python will say "there's no explicit global so I assume this is supposed to be a new local variable." and create one.
Names in python are just references. If python followed the behavior you are expecting you'd need a global for every function you called, let me explain:
def a():
p.append(1) # I should need 'global p' to do this
This would mean if you had
def g():
...
def f():
g() # this would also need 'global g', otherwise how does it see g?
def f2():
global g
def g(): # changes the global g function
return 0

When does Python evaluate a variable in a function definition

I am confused as when does Python evaluates a variable. For example if I do:
p=4
def g():
return p
p=5
print g()
I get 5, so in the definition of g, the variable p remains unevaluated. It is evaluated when we call g
However if I do:
def make_fun():
p=5
def f():
return p
return f
f=make_fun()
p=6
print f()
I get 5. Why does notf contains the unevaluated variable p? I would like to have a clear idea as of when precisely the evaluation of variables takes place.
Python executes the code as it loads the module / script. Hence in your first example it's not "evaluated when we call g", it simply means that the latest value of p is 5 at the time when g is executed. Python will lookup p and it'll return the current value (5).
In your second example p is a local variable. That means it's not affected by p = 6 in the global scope. Or to be clear, the p in f() in your second example is the local variable set within make_fun(). So that's the value you'll get back, it's not the p at the outer (global) scope, which is a different p.
p=4 #you are setting global p to 4
def g():
return p
p=5 #you are setting global p to 5
print g() #you are printing the result of g, which is not setting any value;
so 5 is printed;
but here:
def make_fun():
p=5 #you are setting local p to 5
def f():
return p
return f
f=make_fun()
p=6 #you are setting global p to 6
print f() #you printing the result of a function in f, which is getting the local p
so local p= 5 is printed;
hope i understood you right!
It's not a question of when evaluation takes place. In both of your examples, p is evaluated when the function is called. The real question is when does that variable get declared.
In your first example, p is declared outside of the function and therefore has global scope. Any changes to p impact it wherever it it referenced.
In your second example, you are declaring another variable p inside the function. This is local to the function and not impacted by any code outside of the function. Even if you then go ahead and declare another global p as you do in the line after f=make_fun(), this global variable is overridden within the function by the local variable.
As an experiment, try printing p after you print f() and you'll see that it's value will be 6 as you assigned it.

Categories