Why do ints and dicts behave differently in nested functions? [duplicate] - python

I'd like to have variable defined in the nesting function to be altered in the nested function, something like
def nesting():
count = 0
def nested():
count += 1
for i in range(10):
nested()
print count
When nesting function is called, I wish it prints 10, but it raises UnboundLocalError. The key word global may resolve this. But as the variable count is only used in the scope of nesting function, I expect not to declare it global. What is the good way to do this?

In Python 3.x, you can use the nonlocal declaration (in nested) to tell Python you mean to assign to the count variable in nesting.
In Python 2.x, you simply can't assign to count in nesting from nested. However, you can work around it by not assigning to the variable itself, but using a mutable container:
def nesting():
count = [0]
def nested():
count[0] += 1
for i in range(10):
nested()
print count[0]
Although for non-trivial cases, the usual Python approach is to wrap the data and functionality in a class, rather than using closures.

A little bit late, you can attach an attribute to "nesting" function like so:
def nesting():
def nested():
nested.count += 1
nested.count = 0
for i in range(10):
nested()
return nested
c = nesting()
print(c.count)

The most elegant approach for me: Works 100% on both python versions.
def ex8():
ex8.var = 'foo'
def inner():
ex8.var = 'bar'
print 'inside inner, ex8.var is ', ex8.var
inner()
print 'inside outer function, ex8.var is ', ex8.var
ex8()
inside inner, ex8.var is bar
inside outer function, ex8.var is bar
More: http://www.saltycrane.com/blog/2008/01/python-variable-scope-notes/

Related

Changing a variable inside a method with another method inside it [duplicate]

This question already has answers here:
Is it possible to modify a variable in python that is in an outer (enclosing), but not global, scope?
(9 answers)
Closed 7 months ago.
The following code raises an UnboundLocalError:
def foo():
i = 0
def incr():
i += 1
incr()
print(i)
foo()
Is there a way to accomplish this?
Use nonlocal statement
def foo():
i = 0
def incr():
nonlocal i
i += 1
incr()
print(i)
foo()
For more information on this new statement added in python 3.x, go to https://docs.python.org/3/reference/simple_stmts.html#the-nonlocal-statement
You can use i as an argument like this:
def foo():
i = 0
def incr(i):
return i + 1
i = incr(i)
print(i)
foo()
See 9.2. Python Scopes and Namespaces:
if no global statement is in effect – assignments to names always go into the innermost scope.
Also:
The global statement can be used to indicate that particular variables live in the global scope and should be rebound there; the nonlocalstatement indicates that particular variables live in an enclosing scope and should be rebound there.
You have many solutions:
Pass i as an argument ✓ (I would go with this one)
Use nonlocal keyword
Note that in Python2.x you can access non-local variables but you can't change them.
People have answered your question but no one seems to address why exactly this is happening.
The following code raises an UnboundLocalError
So, why? Let's take a quote from the FAQ:
When you make an assignment to a variable in a scope, that variable becomes local to that scope and shadows any similarly named variable in the outer scope.
Inside your nested function you are performing an assignment with the += operator. What this means is that i += 1 will approximately perform i = i + 1 (from a binding perspective). As a result i in the expression i + 1 will be searched for in the local scope (because it is used in the assignment statement) for function incr where it will not be found, resulting in an UnboundLocalError: reference before assignment .
There's many ways you can tackle this and python 3 is more elegant in the approach you can take than python 2.
Python 3 nonlocal:
The nonlocal statements tells Python to look for a name in the enclosing scope (so in this case, in the scope of function foo()) for name references:
def foo():
i = 0
def incr():
nonlocal i
i +=1
incr()
print(i)
Function attributes Python 2.x and 3.x:
Remember that functions are first class objects, as such they can store state. Use function attributes to access and mutate function state, the good thing with this approach is it works on all pythons and doesn't require global, nonlocal statements.
def foo():
foo.i = 0
def incr():
foo.i +=1
incr()
print(foo.i)
The global Statement (Python 2.x, 3.x):
Really the ugliest of the bunch, but gets the job done:
i = 0
def foo():
def incr():
global i
i += 1
incr()
print(i)
foo()
The option of passing an argument to the function gets the same result but it doesn't relate to mutating the enclosing scope in the sense nonlocal and global are and is more like the function attributes presented. It is creating a new local variable in function inc and then re-binding the name to i with the i = incr(i) but, it does indeed get the job done.
In Python, int are immutable. So you could put your int in a mutable object.
def foo():
i = 0
obj = [i]
def incr(obj):
obj[0]+=1
incr(obj)
print(obj[0])
foo()
You can make i global and use it.
i = 0
def foo():
def incr():
global i
i += 1
incr()
print(i)
foo()
but the most preferred way is to pass i as a param to incr
def foo():
i = 0
def incr(arg):
arg += 1
return arg
i = incr(i)
print(i)
foo()
you could also use a lambda function:
def foo():
i=0
incr = lambda x: x+1
print incr(i)
foo()
I think the code is cleaner this way
Simple function attributes will not work in this case.
>>> def foo():
... foo.i = 0
... def incr():
... foo.i +=1
... incr()
... print(foo.i)
...
>>>
>>>
>>> foo()
1
>>> foo()
1
>>> foo()
1
You re assign your foo.i to 0 for every call of foo.
It better to use hassattr. But code become more complicated.
>>> def foo():
... if not hasattr(foo, 'i'):
... foo.i = 0
... def incr():
... foo.i += 1
... return foo.i
... i = incr()
... print(i)
...
>>>
>>>
>>> foo()
1
>>> foo()
2
>>> foo()
3
Also you can try this idea:
>>> def foo():
... def incr():
... incr.i += 1
... return incr.i
... incr.i = 0
... return incr
...
>>> a = foo()
>>> a()
1
>>> a()
2
>>> a()
3
>>> a()
4
May be it is more handy to wrap you incr into decorator.

How to declare a variable is from the enclosing scope?

How do I make code like the following work? I want to reference a variable, for assignment, in the enclosing function scope.
def outer():
x = 0
def inner():
x += 1
inner()
The code as written gives an UnboundLocalError. I understand why I get this error, I just don't know how I indicate that x comes from the wrapping scope.
You can do:
def outer():
x = [0]
def inner():
x[0] += 1
inner()
You can't rebind a non-local, but you can mutate it.
You cannot do what you ask in a clean way. There is nothing analagous to the global statement that can help you. You'll want to code it like this:
def outer():
x = 0
def inner(x):
return x + 1
x = inner(x)
This has the added advantage of making it explicitly clear as to how data passes into, and out of, the function.
Perhaps you will need to replace x with an object whose state can be mutated.

How to rebind names in outer function?

>>> def QQ():
... a = 0
... def QQQ():
... global a
... a += 1
... QQQ()
...
>>> QQ()
NameError: global name 'a' is not defined
It appears that global doesn't work in this scenario. What else can I do, other than the 1-element list trick?
If you're using Python 3.x, you can use nonlocal:
>>> def QQ():
... a = 0
... def QQQ():
... nonlocal a
... a += 1
... QQQ()
... return a
...
>>> QQ()
1
If you're using Python 2.x, you can't do it. That's why nonlocal was added. So, you have to use some kind of mutable wrapper, like the 1-element list trick.*
PEP 3104 explains all of the gory details (including why Python doesn't do "classic static nested scoping" by default, and requires you to be explicit about it).
* Or upgrade to 3.x, of course. Whenever you find yourself asking how to bind to nonlocals, delegate to another generator, get fully-qualified class names, specify keyword-only parameters, unpack a variable-length iterator but capture the last value separately, or anything else that's trivial in 3.x but painful in 2.x, it's worth re-asking yourself whether it's time to upgrade.
Instead of creating an arbitrary list you could use the built in *args and **kwargs something like
def f(**kwargs):
kwargs['start'] = kwargs.get('start', 0)
def g():
kwargs['start'] += 1
return kwargs['start']
return g
it's essentially the same "hack", this is just less ugly imo.

Python closure function losing outer variable access

I just learned python # decorator, it's cool, but soon I found my modified code coming out weird problems.
def with_wrapper(param1):
def dummy_wrapper(fn):
print param1
param1 = 'new'
fn(param1)
return dummy_wrapper
def dummy():
#with_wrapper('param1')
def implementation(param2):
print param2
dummy()
I debug it, it throws out exception at print param1
UnboundLocalError: local variable 'param1' referenced before assignment
If I remove param1 = 'new' this line, without any modify operation (link to new object) on variables from outer scope, this routine might working.
Is it meaning I only have made one copy of outer scope variables, then make modification?
Thanks Delnan, it's essential to closure.
Likely answer from here:
What limitations have closures in Python compared to language X closures?
Similar code as:
def e(a):
def f():
print a
a = '1'
f()
e('2')
And also this seems previous annoying global variable:
a = '1'
def b():
#global a
print a
a = '2'
b()
This is fixed by add global symbol.
But for closure, no such symbol found.
Thanks unutbu, Python 3 gave us nonlocal.
I know from above directly accessing to outer variable is read-only.
but it's kind of uncomfortable to see preceded reading variable(print var) is also affected.
When Python parses a function, it notes whenever it finds a variable used on the left-hand side of an assignment, such as
param1 = 'new'
It assumes that all such variables are local to the function.
So when you precede this assignment with
print param1
an error occurs because Python does not have a value for this local variable at the time the print statement is executed.
In Python3 you can fix this by declaring that param1 is nonlocal:
def with_wrapper(param1):
def dummy_wrapper(fn):
nonlocal param1
print param1
param1 = 'new'
fn(param1)
return dummy_wrapper
In Python2 you have to resort to a trick, such as passing param1 inside a list (or some other mutable object):
def with_wrapper(param1_list):
def dummy_wrapper(fn):
print param1_list[0]
param1_list[0] = 'new' # mutate the value inside the list
fn(param1_list[0])
return dummy_wrapper
def dummy():
#with_wrapper(['param1']) # <--- Note we pass a list here
def implementation(param2):
print param2
You assign param1 in the function, which makes param1 a local variable. However, it hasn't been assigned at the point you're printing it, so you get an error. Python doesn't fall back to looking for variables in outer scopes.
Handy for temporary situations or exploratory code (but otherwise not good practice):
If you're wanting to capture a variable from the outer scope in Python 2.x then using global is also an option (with the usual provisos).
While the following will throw (assignment of outer1 within inner makes it local and hence unbounded in the if condition):
def outer():
outer1 = 1
def inner():
if outer1 == 1:
outer1 = 2
print('attempted to accessed outer %d' % outer1)
This will not:
def outer():
global outer1
outer1 = 1
def inner():
global outer1
if outer1 == 1:
outer1 = 2
print('accessed outer %d' % outer1)

How do I change nesting function's variable in the nested function

I'd like to have variable defined in the nesting function to be altered in the nested function, something like
def nesting():
count = 0
def nested():
count += 1
for i in range(10):
nested()
print count
When nesting function is called, I wish it prints 10, but it raises UnboundLocalError. The key word global may resolve this. But as the variable count is only used in the scope of nesting function, I expect not to declare it global. What is the good way to do this?
In Python 3.x, you can use the nonlocal declaration (in nested) to tell Python you mean to assign to the count variable in nesting.
In Python 2.x, you simply can't assign to count in nesting from nested. However, you can work around it by not assigning to the variable itself, but using a mutable container:
def nesting():
count = [0]
def nested():
count[0] += 1
for i in range(10):
nested()
print count[0]
Although for non-trivial cases, the usual Python approach is to wrap the data and functionality in a class, rather than using closures.
A little bit late, you can attach an attribute to "nesting" function like so:
def nesting():
def nested():
nested.count += 1
nested.count = 0
for i in range(10):
nested()
return nested
c = nesting()
print(c.count)
The most elegant approach for me: Works 100% on both python versions.
def ex8():
ex8.var = 'foo'
def inner():
ex8.var = 'bar'
print 'inside inner, ex8.var is ', ex8.var
inner()
print 'inside outer function, ex8.var is ', ex8.var
ex8()
inside inner, ex8.var is bar
inside outer function, ex8.var is bar
More: http://www.saltycrane.com/blog/2008/01/python-variable-scope-notes/

Categories

Resources