Passing argument from Parent function to nested function Python - python

here is my code:
def f(x):
def g(n):
if n < 10:
x = x + 1
g(n + 1)
g(0)
When I evaluate f(0), there would be an error "x referenced before assignment".
However, when I use "print x" instead of "x = x + 1" , it will work.
It seems that in the scope of g, I can only use x as an "use occurrence" but not a "binding occurrence". I guess the problem is that f passes to g only the VALUE of x.
Am I understanding it correctly or not? If not, can someone explain why the left side of "x = x + 1" is not defined before reference?
Thanks

You are understanding it correctly. You cannot use x to assign to in a nested scope in Python 2.
In Python 3, you can still use it as a binding occurrence by marking the variable as nonlocal; this is a keyword introduced for just this usecase:
def f(x):
def g(n):
nonlocal x
if n < 10:
x = x + 1
g(n + 1)
g(0)
In python 2, you have a few work-arounds; using a mutable to avoid needing to bind it, or (ab)using a function property:
def f(x):
x = [x] # lists are mutable
def g(n):
if n < 10:
x[0] = x[0] + 1 # not assigning, but mutating (x.__setitem__(0, newvalue))
g(n + 1)
g(0)
or
def f(x):
def g(n):
if n < 10:
g.x = g.x + 1
g(n + 1)
g.x = x # attribute on the function!
g(0)

Yes, assigning to names is different than reading their values. Any names that are assigned to in a function are considered local variables of that function unless you specify otherwise.
In Python 2, the only way to "specify otherwise" is to use a global statement to allow you assign to a global variable. In Python 3, you also have the nonlocal statement to assign a value to a variable in a higher (but not necessarily global) scope.
In your example, x is in a higher but non-global scope (the function f). So there is no way to assign to x from within g in Python 2. In Python 3 you could do it with a nonlocal x statement in g.

Related

Why is global keyword not ignored even if it is unreachable? (Python)

I am really new to programming and Python, so please really forgive me for my ignorance.
I just learned that using global can make a variable inside function be a global one.
However, I discovered something not with my expectation:
I tried the following code in Python 3.8 (forgive me for my ignorance as I don't know what else information I should provide):
>>> x = 0
>>>
>>> def function():
... if False:
... global x
... x = 1
...
>>> function()
>>> print(x)
and the result is 1.
However, I expected the code to have the same effect as the following code:
>>> x = 0
>>>
>>> def function():
... x = 1
...
>>> function()
>>> print(x)
which the result should be 0.
In my mind, the statement inside if False should not be executed, so it sounds strange to me.
Also, personally, I think that in some situation I would expect the variable inside a function, whether local or global, to be dependent on other codes... what I mean is, I would like to change if False to something like if A == 'A', while (I hope) I can control whether the x is global/local according to my conditional statement.
I tried to change if to while, but it's the same... there isn't a infinite loop, but the code global x is still executed/compiled...
I admit that it may sounds naive, and perhaps it just won't work in Python, but I really wonder why... It seems that the code global x is unreachable, but how come it is not ignored?
Can anyone please tell me about the reason? I would like to know more about the mechanism behind compilation(?)
Any help would be appreciated, thank you!
In python the global statement (and the nonlocal statement) are very different from the normal python code. Essentially no matter where a global statement in a function is, it influences always the current codeblock, and is never "executed". You should think more of it as a compiler directive instead of a command.
Note that the statement itself must come before any usage of the variable it modifies, i.e.
print(x)
global x
is a syntax error. The global statement can only modify variable behavior in the whole codeblock, you can't first have a non-global variable that later gets global and you can also not have conditional global variable
(I couldn't really find good documentation for this behavior, here it says "The global statement is a declaration which holds for the entire current code block." and "global is a directive to the parser. It applies only to code parsed at the same time as the global statement." but that doesn't seem super clear to me.)
There are more compiler directives in python, although they don't always look like one. One is the from __future__ import statements which look like module imports but change python behavior.
Global is not in execution path but in a scope. The scope is whole function. Statements like if for don't make scopes. If you use any assignment you create local variable. The same with global or nonlocal you bind symbol to variable from outside.
As Stanislas Morbieu typed, see doc.
Programmer’s note: global is a directive to the parser. It applies only to code parsed at the same time as the global statement.
Not at execution time.
x = 1
def fun():
y = x + 1
print(f'local x = {x}, y = {y}')
fun()
print(f'global x = {x}')
# Output:
# local x = 1, y = 2
# global x = 1
In example above, y uses global x (and adds 1).
x = 1
def fun():
y = x
x = y + 1
print(f'local x = {x}')
fun()
print(f'global x = {x}')
# Output:
# UnboundLocalError: local variable 'x' referenced before assignment
Look at last example. It doesn't assign y from global x because assignment in second line creates local x and y can not read local x before x assignment. The same with:
x = 1
def fun():
if False:
x += 1
fun()
# Output
# UnboundLocalError: local variable 'x' referenced before assignment
x assignment creates local variable.
If you want to change global variable under condition you can use globals().
x = 1
def set_x(do_set, value):
if do_set:
globals()['x'] = value
print(f'global x = {x} (init)')
set_x(False, 2)
print(f'global x = {x} (false)')
set_x(True, 3)
print(f'global x = {x} (true)')
# Output
# global x = 1 (init)
# global x = 1 (false)
# global x = 3 (true)
Proxy
I you want to decide with variable you want to use later (in the same scope) you need some kind of proxy IMO.
x = 1
def fun(use_global):
x = 2 # local
scope = globals() if use_global else locals()
scope['x'] += 1
print(f'local ({use_global}): x = {scope["x"]}')
print(f'global: x = {x} (init)')
fun(False)
print(f'global: x = {x} (false)')
fun(True)
print(f'global: x = {x} (true)')
# Output:
# global: x = 1 (init)
# local (False): x = 3
# global: x = 1 (false)
# local (True): x = 2
# global: x = 2 (true)
Maybe you can think about refactoring of your code if you need it.
If you can change local variable name (if not use globals() as above), you can proxy:
use dict (like in example above)
use list (x=[1]) and usage x[0]
use object (with builtin dict), example:
class X:
def __init__(self, x):
self.x = x
x = X(1)
def fun(use_global):
global x
my_x = x if use_global else X(2)
my_x.x += 1
print(f'local ({use_global}): x = {my_x.x}')
print(f'global: x = {x.x} (init)')
fun(False)
print(f'global: x = {x.x} (false)')
fun(True)
print(f'global: x = {x.x} (true)')
# Output:
# global: x = 1 (init)
# local (False): x = 3
# global: x = 1 (false)
# local (True): x = 2
# global: x = 2 (true)
Note. Variables in Python are only references. It is way you can not change x = 1 without global (or globals()). You change reference to local value 1.
But you can change z[0] or z['x'] or z.x. Because z referents to list or dict or object and you modify it content.
See: https://realpython.com/python-variables/#object-references
You can check real object by id() function, ex. print(id(x), id(my_x)).
As per the Python documentation, global is a directive to the parser so it is taken into account before the execution, therefore it does not matter if the code is reachable or not. The variable is global for the entire scope, which is the function in your case.

The solution always returns x=0

On using this code I get the value returned 0 always
def fi(arr,mini):
print(arr)
if(len(arr)<3):
x = mini
return
for j in range(1,(math.ceil(len(arr)/2) )):
l1 = 2*j+1
if(med(arr[0:l1])<mini):
mini= med(arr[0:l1])
print("hello", mini)
fi(arr[1:],mini)
return x
You define x only if len(arr) < 3. In other cases it is unassigned. You should add a default value before the if block, or within an else block depending on what you are trying to do.
You haven't declared x before assigning it to mini. What should your function return by default?
you define local variable 'x' whose scope is limited to if block and you are trying to return this variable from the function, so it's getting undefined. Better define local variable x in the function and give it some default value which should be return by the function.
def fi(arr,mini):
x = some_default_value
print(arr)
if(len(arr)<3):
x = mini
return
for j in range(1,(math.ceil(len(arr)/2) )):
l1 = 2*j+1
if(med(arr[0:l1])<mini):
mini= med(arr[0:l1])
print("hello", mini)
fi(arr[1:],mini)
return x

compile error when using outer defined variable from inner function in python [duplicate]

here is my code:
def f(x):
def g(n):
if n < 10:
x = x + 1
g(n + 1)
g(0)
When I evaluate f(0), there would be an error "x referenced before assignment".
However, when I use "print x" instead of "x = x + 1" , it will work.
It seems that in the scope of g, I can only use x as an "use occurrence" but not a "binding occurrence". I guess the problem is that f passes to g only the VALUE of x.
Am I understanding it correctly or not? If not, can someone explain why the left side of "x = x + 1" is not defined before reference?
Thanks
You are understanding it correctly. You cannot use x to assign to in a nested scope in Python 2.
In Python 3, you can still use it as a binding occurrence by marking the variable as nonlocal; this is a keyword introduced for just this usecase:
def f(x):
def g(n):
nonlocal x
if n < 10:
x = x + 1
g(n + 1)
g(0)
In python 2, you have a few work-arounds; using a mutable to avoid needing to bind it, or (ab)using a function property:
def f(x):
x = [x] # lists are mutable
def g(n):
if n < 10:
x[0] = x[0] + 1 # not assigning, but mutating (x.__setitem__(0, newvalue))
g(n + 1)
g(0)
or
def f(x):
def g(n):
if n < 10:
g.x = g.x + 1
g(n + 1)
g.x = x # attribute on the function!
g(0)
Yes, assigning to names is different than reading their values. Any names that are assigned to in a function are considered local variables of that function unless you specify otherwise.
In Python 2, the only way to "specify otherwise" is to use a global statement to allow you assign to a global variable. In Python 3, you also have the nonlocal statement to assign a value to a variable in a higher (but not necessarily global) scope.
In your example, x is in a higher but non-global scope (the function f). So there is no way to assign to x from within g in Python 2. In Python 3 you could do it with a nonlocal x statement in g.

Python 3 syntax should work, but doesn't

It's so basic it should work. I want a function that adds a value to something
There must be something I don't know about python 3, so here we go.
x = 0
def foo(x=x): ##### with out x=x there is a error
x = x + 1 # option one
x = 1 # option two
# when we run it
foo()
print(x)
# it returns 0, it should return 1
x is a local variable in foo(); Assigning x as a default value for a keyword argument won't make it any less a local.
If you wanted it to be a global, mark it as such:
x = 0
def foo():
global x
x = x + 1
print(x)
foo()
print(x)
but you probably just wanted to pass in the value as an argument instead:
def foo(value):
return value + 1
x = 0
print(x)
x = foo(x)
print(x)
This is basically and example of scoping rules. The variable x within foo is local to foo, so nothing that happens to the local x changes anything outside foo, including the global x with which is actually a different variable. When the interpreter exits foo the global x comes back into scope and it hasn't changed from its initial value of 0. The function header foo(x=x) defines a local x whose default value is the global x. The interpreter allows it but it's generally considered bad programming practice to have the same variable name representing two variables because it leads to this kind of confusion.

Closure in python?

When I run this code, I get this result:
15
15
I expect the output should be
15
17
but it is not. The question is: why?
def make_adder_and_setter(x):
def setter(n):
x = n
return (lambda y: x + y, setter)
myadder, mysetter = make_adder_and_setter(5)
print myadder(10)
mysetter(7)
print myadder(10)
You are setting a local variable x in the setter() function. Assignment to a name in a function marks it as a local, unless you specifically tell the Python compiler otherwise.
In Python 3, you can explicitly mark x as non-local using the nonlocal keyword:
def make_adder_and_setter(x):
def setter(n):
nonlocal x
x = n
return (lambda y: x + y, setter)
Now x is marked as a free variable and looked up in the surrounding scope instead when assigned to.
In Python 2 you cannot mark a Python local as such. The only other option you have is marking x as a global. You'll have to resort to tricks where you alter values contained by a mutable object that lives in the surrounding scope.
An attribute on the setter function would work, for example; setter is local to the make_adder_and_setter() scope, attributes on that object would be visible to anything that has access to setter:
def make_adder_and_setter(x):
def setter(n):
setter.x = n
setter.x = x
return (lambda y: setter.x + y, setter)
Another trick is to use a mutable container, such as a list:
def make_adder_and_setter(x):
x = [x]
def setter(n):
x[0] = n
return (lambda y: x[0] + y, setter)
In both cases you are not assigning to a local name anymore; the first example uses attribute assignment on the setter object, the second alters the x list, not assign to x itself.
Python 2.x has a syntax limitation that doesn't allow to capture a variable in read/write.
The reason is that if a variable is assigned in a function there are only two possibilities:
the variable is a global and has been declared so with global x
the variable is a local of the function
more specifically it's ruled out that the variable is a local of an enclosing function scope
This has been superseded in Python 3.x with the addition of nonlocal declaration. Your code would work as expected in Python 3 by changing it to
def make_adder_and_setter(x):
def setter(n):
nonlocal x
x = n
return (lambda y: x + y, setter)
The python 2.x runtime is able to handle read-write closed over variable at a bytecode level, however the limitation is in the syntax that the compiler accepts.
You can see a lisp compiler that generates python bytecode directly that creates an adder closure with read-write captured state at the end of this video. The compiler can generate bytecode for Python 2.x, Python 3.x or PyPy.
If you need closed-over mutable state in Python 2.x a trick is to use a list:
def make_adder_and_setter(x):
x = [x]
def setter(n):
x[0] = n
return (lambda y: x[0] + y, setter)
Your inner def setter(n) function defines its own local variable x. That hides the other x variable that was a parameter of make_adder_and_setter (makes a hole in the scope). So the setter function has no side effect. It just sets the value of an inner local variable and exits.
Maybe it will be clear for you if you try the code below. It does exactly the same thing, just uses the name z instead of x.
def make_adder_and_setter(x):
def setter(n):
z = n
return (lambda y: x + y, setter)
myadder, mysetter = make_adder_and_setter(5)
print myadder(10)
mysetter(7)
print myadder(10)

Categories