Python and closed variables [duplicate] - python

This question already has answers here:
nonlocal keyword in Python 2.x
(10 answers)
Closed 6 months ago.
Have a look at this code:
def closure():
value = False
def method_1():
value = True
def method_2():
print 'value is:', value
method_1()
method_2()
closure()
I would expect it to print 'Value is: True' but it doesn't. Why is this and what is the solution?

This happens because method_1 gets its own local scope where it can declare variables. Python sees value = True and thinks you're creating a new variable named value, local to method_1.
The reason Python does this is to avoid polluting the outer scope's locals with variables from an inner function. (You wouldn't want assignments in regular, module-level functions to result in global variables being created!)
If you don't assign to value, then Python searches the outer scopes looking for the variable (so reading the variable works as expected, as demonstrated by your method_2).
One way to get around this is by using a mutable object instead of assigment:
result = { 'value': False }
def method_1():
result['value'] = True
In Python 3, the nonlocal statement (see also docs) was added for exactly this scenario:
def method_1():
nonlocal value
value = True # Works as expected -- assigns to `value` from outer scope

In method_1, Python assumes (quite sensibly!) that value is a local variable. Whenever you assign to a variable name inside a function, it is assumed that that variable name is a new local variable. If you want it to be global, then you have to declare it as global, and if you want it to be "nonlocal", in Python 3, you can declare it nonlocal, but in Python 2, you have to do something uglier: store the value in a container. That avoids having to reassign the variable name, and so avoids the scoping ambiguity.
def method_1_global():
global value
value = True
def method_1_nonlocal_P3():
nonlocal value
value = True
value = [False]
def method_1_nonlocal_P2():
value[0] = True

When you assign to a variable, it assumes the variable is of local scope. So the value in method_1 is not the value in closure.
If you want this to work on Python 3, add a line to method_1: nonlocal value.
On Python 2,
def closure():
value = [False]
def method_1():
value[0] = True
def method_2():
print 'value is:', value
method_1()
method_2()
closure()
is one possible work-around.

This is because you are not actually modified the closed-over variable - you are masking it with a new one that has the same name. In python 2.x there is no easy way around this, which is why the nonlocal keyword was added in python 3.
This can be worked around, however, using mutable types like list and dictionary. There is a good example in this answer.

To avoid this, you can use a list.
value = [False]
def method_1():
value[0] = True
What Python does now is searching in higher levels of the scope as value is not available in the local variables. As value is a list and Python refernces it as a global variable relative to *method_1*, you can treat value as it is, a list.

Related

Use a dictionary to refer to a global variable inside a function (Python)

I'm using a dictionary to refer to a global variable in Python inside a function. I want to use the function to update the global variable. (In reality my global variables are more complicated than this.)
global_variable_A=5
global_variable_B=1
dictionary={'A':global_variable_A,'B':global_variable_B}
def f(x,glob):
global global_variable_A
dictionary[glob]+=x
f(2,'A')
print(global_variable_A)
This returns 5 rather than 7. I understand why, is there anyway to let Python know that I mean the variable dictionary[glob] to refer to the global rather than the local variable, while still referring to the variable through a dictionary?
Thanks for your time, and apologies if I've missed something completely obvious.
When you assign a value to a name name = 5, you're creating a reference to 5 that you can use the identifier name to access. Normally, if you then have some code with a narrower scope, you can either use that reference
def f():
print(name)
or create a local reference using the same identifier, potentially to an unrelated value
def g():
name = 100
print(name)
The global keyword allows you to instead manipulate the identifier as if you weren;t in the more narrow scope, allowing you to reassign the global name to a different reference:
def h():
global name
name = 100
h()
print(name) # 100
However, when you use a reference to create another reference, there isn't any relation ship between those two references. So
name = 5
l = [name]
leaves us with two references to the value 5: one from the identifier name, and one from the first position of l. Crucially, those two references are not related; we can change one without changing the other.
name = 6
print(l) # [5]
One way to accomplish what you want is to use a boxed type. That means that you create an object that points to another object. All of your references can then point to the first object (the box) and you can freely change what it points to (the thing "inside" the box), by only updating a single reference:
class Box:
def __init__(self, value):
self.value = value
box = Box(5)
l = [box]
box.value = 10
print(l[0].value) # 10
If you think you need to do this, there's a design flaw in your code. I really wouldn't recommend doing this.
global_a = 5
def add_to_global(key, value):
globals()[key] += value
add_to_global("global_a", 2)
print(global_a)
Output:
7

Merging global and local scopes

If this works
x=5
def main():
for globe in locals():
globals().update(locals()[globe])
print x
main()
then why doesn't this?
x=5
def main():
for globe in locals():
globals().update(locals()[globe])
x+=1
print x
main()
The error in the latter statement claims that x is referenced before assignment, however it works in the first example...
In python when you assign a variable the declaration happens automatically.
So when you assign a value to x inside the function, python think that is a new local variable, shadowing the global x.
if you want to assign a value to the global x you can do this:
x=5
def main():
global x
x += 1
print x
main()
You cannot assign a global variable in python without explicitly doing so. By writing x+=1 you are assigning a value to x and are implicitly declaring x as a local variable. But it is not defined and therefore you get an error.
The loop has no actual effect, as the locals dictionary is empty.
If you want to use global variables in Python (which you shouldn't, but that's another matter), you should use the global keyword.
suffixing 1 and 2 your two functions, you can find local names (syntax slightly different in python 2) :
In [7]: main1.__code__.co_varnames
Out[7]: ('globe',)
In [8]: main2.__code__.co_varnames
Out[8]: ('globe', 'x')
So x have different status. In the second case the local x mask the global one, so x=x+1 cause the error, because not yet defined.
From docs :
If a name is bound in a block, it is a local variable of that block, unless declared as nonlocal or global.(...).If a variable is used in a code block but not defined there, it is a free variable.

Function within a function to alter a variable

This is a simplified section of code from a game I'm trying to make. There is a condition in primary_function that if satisfied will run the action, in this case secondary_function fed into it, action itself being a function which alters a particular variable. Problem is that it doesn't alter the variable. Print(variable) returns 1.
variable = 1
def secondary_function(var):
var += 1
def function(parameter_1, parameter_2, action = None, var = None):
if condition == 'satisfied':
action(var)
function(1, 2, secondary_function, variable)
print(variable)
Variables in Python are not passed by reference, and there is no way to do so. So a function that takes an integer argument and just increments it can never impact the original variable. If you want to modify the original variable, you will have to assign it a new value instead.
def secondary_function(var):
return var + 1
variable = 1
variable = secondary_function(variable) # variable is now 2
Similary, you will have to modify your function function too to return the value instead:
def function (parameter_1, parameter_2, action = None, var = None):
if parameter_1 == 1:
return action(var)
variable = function(1, 2, secondary_function, variable)
Python has no notion of pass-by-reference or pass-by-value. Instead, every variable is passed by assignment. That means that whenever you call a function and pass a variable, the behavior is the same as if you just did function_argument = variable; you are assigning the value of the source variable to the function argument. Since Python is object oriented, this effectively means that you copy the reference to the object that variable refers to. As such, when the object is mutable, you can modify it and impact other variables referencing the same object. But for immutable objects (ints, strings, tuples, etc.), you cannot modify the existing object, so when you “modify” it, you always assign a new object to the variable, without affecting all the other variables that still point to the old object.
Function arguments in Python as passed by value. Thus, function(var) sees a copy of variable in var and secondary_function(var) sees yet another copy.

How to create variables local to with statement?

I would like to create temporary variables visible in a limited scope.
It seems likely to me that you can do this with a "with" statement, and I would think there is a construct that makes it easy to do, but I cannot seem to find it.
I would like something like the following (but it does not work this way of course):
pronunciation = "E_0 g z #_1 m p l"
# ...
with pronunciation.split() as phonemes:
if len(phonemes) > 2 or phonemes[0].startswith('E'):
condition = 1
elif len(phonemes) < 3 and phonemes[-1] == '9r':
condition = 2
So is there a simple way to make this work, using built-ins?
Thanks!
Python creates local variables with function scope (once a name is used it stays alive until the end of the function).
If you really want to limit scope then "del <var>" when you want it explicitly discarded, or create separate function to act as a container for a more limited scope.
You can create a method
def process_pronunciation(pronunciation):
phonemes = pronunciation.split()
if len(phonemes) > 2 or phonemes[0].startswith('E'):
condition = 1
elif len(phonemes) < 3 and phonemes[-1] == '9r':
condition = 2
return condition
When you call the method, the local variable phonemes won't be available in the global namespace.
pronunciation = "E_0 g z #_1 m p l"
condition = process_phonemes(pronunciation)
You could do it with with, but I don't think it's worth the trouble. Basically (in a python function) you have two scopes - global or local, that's it. If you want a symbol to have a lifespan shorter than the function you'll have to delete it afterwards using del. You could define your own context manager to make this happen:
class TempVar:
def __init__(self, loc, name, val):
self.loc = loc
self.name = name
self.val
def __enter__(self):
if self.name in self.loc:
self.old = self.loc[self.name]
self.loc[self.name] = self.val
def __exit__(self, *exc):
if hasattr(self, "old"):
self.loc[self.name] = self.old
else:
del self.loc[self.name]
then you can use it to get a temporary variable:
with TempVar(locals(), "tempVar", 42):
print(tempVar)
The working is that it modifies the dict containing local variables (which is supplied to the constructor via locals()) on entry and restoring it when leaving. Please note that this relies on that modifying the result returned by locals() actually modifies the local namespace - the specification does NOT guarantee this behaviour.
Another (and safer) alternative that was pointed out is that you could define a separate function which would have it's own scope. Remember it's perfectly legal to nest functions. For example:
def outer():
def inner(tempVar):
# here tempVar is in scope
print(tempVar)
inner(tempVar = 42)
# here tempVar is out of scope
with statement does not have its own scope , it uses the surrounding scope (like if the with statement is directly inside the script , and not within any function, it uses global namespace , if the with statement is used inside a function, it uses the function's namespace(scope)).
If you want the statements inside a with block to run in its own local scope, one possible way would be to move the logic to a function , that way the logic would be running in its own scope (and not the surrounding scope of with.
Example -
def function_for_with(f):
#Do something.
with pronunciation.split() as phonemes:
function_for_with(phonemes)
Please note, the above will not stop phonemes from being defined in the surrounding scope.
If you want that as well (move the phonemes into its own scope), you can move the complete with statement inside a function. Example -
def function_with(pronunciation):
with pronunciation.split() as phonemes:
#do stuff
pronunciation = "E_0 g z #_1 m p l"
function_with(pronunciation)
Expanding on #skyking's answer, here's an even more magical implementation of the same idea that reads almost exactly like you wrote. Introducing: the with var statement!1
class var:
def __init__(self, value):
import inspect
self.scope = inspect.currentframe().f_back.f_locals
self.old_vars = set(self.scope.keys())
self.value = value
def __enter__(self):
return self.value
def __exit__(self, type, value, traceback):
for name in set(self.scope.keys()) - self.old_vars:
del self.scope[name]
### Usage:
line = 'a b c'
with var (line.split()) as words:
# Prints "['a', 'b', 'c']"
print(words)
# Causes a NameError
print(words)
It does all the nasty extracting of local variables and names for you! How swell. If you space it quirkily like I did and hide the definition in a from boring_stuff import * statement, you can even pretend var is a keyword to all of your confused co-workers.
[1] If you actually use this, the ghost of a dead parrot will probably haunt you forever. The other answers provide much saner solutions; this one is more of a joke.

Confusing change of scope - what's going on?

def Test(value):
def innerFunc():
print value
innerFunc()
def TestWithAssignment(value):
def innerFunc():
print value
value = "Changed value"
innerFunc()
Test("Hello 1")
# Prints "Hello 1"
TestWithAssignment("Hello 2")
# Throws UnboundLocalError: local variable 'value' referenced before assignment
# on the "print value" line
Why does the second one fail, given that the only difference is an assignment which comes after the print statement? I am pretty confused about this.
The issue is that Python wants you to be explicit and you want to be implicit. The execution model that Python uses binds names to the nearest available enclosing scope.
def Test(value):
# Local Scope #1
def innerFunc():
# Local Scope #2
print value
# No immediate local in Local Scope #2 - check up the chain
# First find value in outer function scope (Local Scope #1).
# Use that.
innerFunc()
def TestWithAssignment(value):
# Local Scope #1
def innerFunc():
# Local Scope #2
print value
# Immediate local variable found in Local Scope #2.
# No need to check up the chain.
# However, no value has been assigned to this variable yet.
# Throw an error.
value = "Changed value"
innerFunc()
There is not (as far as I know) a way to walk up the scope in Python 2.x - you have globals() and locals() - but any scopes between the global and the local scope cannot be accessed (if this is not true, I'd love to be corrected).
However, you can pass the local variable value into your inner local scope:
def TestWithAssignment(value):
def innerFunc(value):
print value
# Immediate local **and assigned now**.
value = "Changed value"
# If you need to keep the changed value
# return value
innerFunc(value)
# If you need to keep the changed value use:
# value = innerFunc(value)
In Python 3 you have the new nonlocal statement that can be used to refer to the containing scope (Thanks #Thomas K).
def TestWithAssignment(value):
def innerFunc():
nonlocal value
print value
value = "Changed value"
innerFunc()
This should fix it:
def Test(value):
def innerFunc(value):
print value
innerFunc(value)

Categories

Resources