I'm trying to make a simple Python decorator, below is a simplified example of my use case.
def decorator(x):
def inner(f):
if x == 2:
x = 1
return f
return inner
#decorator(2)
def f():
pass
But I get this error
if x == 2:
UnboundLocalError: local variable 'x' referenced before assignment
Why is x not available? It's confusing because if I use print(x) instead of if x == 2 it works as expected.
Python can't determine xs actual scope, so before it runs, in the inner function it would raise the UnboundLocalError.
The way to fix it is modify the arguments of inner a bit, instead of:
def inner(f):
Make it to be:
def inner(f, x=x):
Then your code would work fine.
Or you could also use nonlocal, by adding nonlocal x in the beginning of the function inner.
def inner(f):
nonlocal x
if x == 2:
x = 1
return f
I have tried creating a function like this:
n = ["one", "two"]
result=""
def join_strings(words):
for word in words:
result+=word
return result
print (join_strings(n))
This does not work and outputs:
UnboundLocalError: local variable 'result' referenced before assignment
The result variable has to be inside the function for it to work.
However, when I have this simple function:
x=3
def price():
return (x+3)
print (price())
This works and prints the price, even though x is outside of the function. Why is this happening?
Actually there is no inconsistency as the examples you gave are different from each other. It would still fail in the second function if you have tried to assign x to itself, like:
>>> x = 3
>>> def price():
... x +=3
... return x
...
>>> price()
UnboundLocalError: local variable 'x' referenced before assignment
Instead of assigning back to x if you choose another name, it would run with no problem:
>>> def price():
... y = x + 3
... return y
...
>>> price()
6
But, why it happens?
It's because Python's scoping rules. You can read the value of a variable outside of function but you can't change it**. When you do x += 3, which is same as x = x + 3 for integers, that means "I have a variable x that I have write access to it in the current scope." You don't have such variable, thus as the error says: you are referencing a "local variable" before assignment.
Is there a way to modify them in the function?
Yes. You can use global keyword, changes will be applied to your global variable:
>>> x = 3
>>> def price():
... global x
... x += 3
... return x
...
>>> x
3
>>> price()
6
>>> x
6
** By changing, I mean assigning something else to it so it's id will change.
The difference is that in the second example you aren't trying to modify or reassign x. (You'll get the same error if you say something like x += 3 in the price function.) Once you use an assignment operator, you're binding a new value to that name, shadowing the outer scope.
If you want to be able to modify something from an outer scope, put it inside a mutable container (like a list); you can then modify the contents without reassigning that variable.
result = [""]
def join_strings(words):
for word in words:
result[0] += word
return result[0]
If you just want to be able to reference the result value, without modifying it, that's fine too, but you have to assign it to a new variable then:
result = ""
def join_strings(words):
ret = result
for word in words:
ret += word
return ret
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.
Consider the following code, I expected the output to be 2 since the x to be printed is a local variable in the scope of inner function. However, it printed 2 instead. I don't understand why that is the case. Any explanation and/or comment is appreciated.
def outer():
x = 1
def inner():
x = 1
x += 1
print x
return x
There are two separate x variables here: the outer function has an x variable and the inner function has its own x variable.
From the moment there is an assignment to a variable somewhere in a function, the variable has a local scope.
So when you call outer(), you will return 1. The x in the inner function is a different one, and furthermore inner() is never called. As a result, the (local) x is not printed.
If you thus would have written:
def outer():
x = 3
def inner():
x = 1
x += 1
print x
return x
Then calling outer() will return 3. Even if you would have called inner() in the outer() function, it would not make any difference. Since the x in the inner() function is another one than the one in the outer() function (there is a local scope defined in inner). Although it would mean that you print 2, you will return 3.
Where X is any programming language (C#, Javascript, Lisp, Perl, Ruby, Scheme, etc) which supports some flavour of closures.
Some limitations are mentioned in the Closures in Python (compared to Ruby's closures), but the article is old and many limitations do not exist in modern Python any more.
Seeing a code example for a concrete limitation would be great.
Related questions:
Can you explain closures (as they relate to Python)?
What is a ‘Closure’?
How does a javascript closure work ?
The most important limitation, currently, is that you cannot assign to an outer-scope variable. In other words, closures are read-only:
>>> def outer(x):
... def inner_reads():
... # Will return outer's 'x'.
... return x
... def inner_writes(y):
... # Will assign to a local 'x', not the outer 'x'
... x = y
... def inner_error(y):
... # Will produce an error: 'x' is local because of the assignment,
... # but we use it before it is assigned to.
... tmp = x
... x = y
... return tmp
... return inner_reads, inner_writes, inner_error
...
>>> inner_reads, inner_writes, inner_error = outer(5)
>>> inner_reads()
5
>>> inner_writes(10)
>>> inner_reads()
5
>>> inner_error(10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 11, in inner_error
UnboundLocalError: local variable 'x' referenced before assignment
A name that gets assigned to in a local scope (a function) is always local, unless declared otherwise. While there is the 'global' declaration to declare a variable global even when it is assigned to, there is no such declaration for enclosed variables -- yet. In Python 3.0, there is (will be) the 'nonlocal' declaration that does just that.
You can work around this limitation in the mean time by using a mutable container type:
>>> def outer(x):
... x = [x]
... def inner_reads():
... # Will return outer's x's first (and only) element.
... return x[0]
... def inner_writes(y):
... # Will look up outer's x, then mutate it.
... x[0] = y
... def inner_error(y):
... # Will now work, because 'x' is not assigned to, just referenced.
... tmp = x[0]
... x[0] = y
... return tmp
... return inner_reads, inner_writes, inner_error
...
>>> inner_reads, inner_writes, inner_error = outer(5)
>>> inner_reads()
5
>>> inner_writes(10)
>>> inner_reads()
10
>>> inner_error(15)
10
>>> inner_reads()
15
The only difficulty I've seen people encounter with Python's in particular is when they try to mix non-functional features like variable reassignment with closures, and are surprised when this doesn't work:
def outer ():
x = 1
def inner ():
print x
x = 2
return inner
outer () ()
Usually just pointing out that a function has its own local variables is enough to deter such silliness.
A limitation (or "limitation") of Python closures, comparing to Javascript closures, is that it cannot be used for effective data hiding
Javascript
var mksecretmaker = function(){
var secrets = [];
var mksecret = function() {
secrets.push(Math.random())
}
return mksecret
}
var secretmaker = mksecretmaker();
secretmaker(); secretmaker()
// privately generated secret number list
// is practically inaccessible
Python
import random
def mksecretmaker():
secrets = []
def mksecret():
secrets.append(random.random())
return mksecret
secretmaker = mksecretmaker()
secretmaker(); secretmaker()
# "secrets" are easily accessible,
# it's difficult to hide something in Python:
secretmaker.__closure__[0].cell_contents # -> e.g. [0.680752847190161, 0.9068475951742101]
Fixed in Python 3 via the nonlocal statement:
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.
#John Millikin
def outer():
x = 1 # local to `outer()`
def inner():
x = 2 # local to `inner()`
print(x)
x = 3
return x
def inner2():
nonlocal x
print(x) # local to `outer()`
x = 4 # change `x`, it is not local to `inner2()`
return x
x = 5 # local to `outer()`
return (inner, inner2)
for inner in outer():
print(inner())
# -> 2 3 5 4
comment for #Kevin Little's answer to include the code example
nonlocal does not solve completely this problem on python3.0:
x = 0 # global x
def outer():
x = 1 # local to `outer`
def inner():
global x
x = 2 # change global
print(x)
x = 3 # change global
return x
def inner2():
## nonlocal x # can't use `nonlocal` here
print(x) # prints global
## x = 4 # can't change `x` here
return x
x = 5
return (inner, inner2)
for inner in outer():
print(inner())
# -> 2 3 3 3
On the other hand:
x = 0
def outer():
x = 1 # local to `outer`
def inner():
## global x
x = 2
print(x) # local to `inner`
x = 3
return x
def inner2():
nonlocal x
print(x)
x = 4 # local to `outer`
return x
x = 5
return (inner, inner2)
for inner in outer():
print(inner())
# -> 2 3 5 4
it works on python3.1-3.3
The better workaround until 3.0 is to include the variable as a defaulted parameter in the enclosed function definition:
def f()
x = 5
def g(y, z, x=x):
x = x + 1