>>> 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.
Related
Is it possible to assign a function to a variable with modified default arguments?
To make it more concrete, I'll give an example.
The following obviously doesn't work in the current form and is only meant to show what I need:
def power(a, pow=2):
ret = 1
for _ in range(pow):
ret *= a
return ret
cube = power(pow=3)
And the result of cube(5) should be 125.
functools.partial to the rescue:
Return a new partial object which when called will behave like func called with the positional arguments args and keyword arguments keywords. If more arguments are supplied to the call, they are appended to args. If additional keyword arguments are supplied, they extend and override keywords.
from functools import partial
cube = partial(power, pow=3)
Demo:
>>> from functools import partial
>>>
>>> def power(a, pow=2):
... ret = 1
... for _ in range(pow):
... ret *= a
... return ret
...
>>> cube = partial(power, pow=3)
>>>
>>> cube(5)
125
The answer using partial is good, using the standard library, but I think it's worth mentioning that the following approach is equivalent:
def cube(a):
return power(a, pow=3)
Even though this doesn't seem like assignment because there isn't a =, it is doing much the same thing (binding a name to a function object). I think this is often more legible.
In specific there's a special function for exponents:
>>> 2**3
8
But I also solved it with a lambda function, which is a nicer version of a function pointer.
# cube = power(pow=3) # original
cube = lambda x: power(x,3)
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.
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/
Is there a way to use variable like in operator overloading.
e.g.
a += 1
Instead of a = a + 1
in
a = max(a, some_other_variable)
The max() function is just an example.
NOTE:
My intent here is not to use the variable 'a' again, if possible. These two examples are different and not related to each other.
e.g.
a = some_function(a, b)
Here, the values returned from some_function() is assigned back to variable 'a' again.
Unless variable 'a' is a class variable I cannot access variable inside function some_function(), although if there is a way so that I can use it only once?
You cannot supplement Python's set of operators and statements directly in the Python code. However, you can write a wrapper that uses Python's language services to write a Pythonesque DSL which includes the operators you want.
I feel like you want something along these lines ...
>>> class Foo(object):
... def __iadd__(self, other):
... return max(self.num, other)
... def __init__(self, num):
... self.num = num
...
>>> a = Foo(5)
>>> a += 4
>>> print a
5
>>> a = Foo(4)
>>> a += 6
>>> a
6
But please note that I would consider this use of __iadd__ to be very impolite. Having __iadd__ return something other than self is generally inconsiderate if the type is mutable.
Instead of overloading an operator in a like the other answer, you could create a partial-like object for the second part. (I used the left shift operator for "coolness")
class partial(functools.partial):
def __rlshift__(self, val):
return self(val)
and use like this:
>>> a = 10
>>> a <<= partial(max, 20)
>>> a
20
So you don't need to mess with your variable types to execute the operation. Also you will not need to declare a new class for every function.
PS: Beware that the actual execution is max(20, a).
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/