What are the meanings and differences between "uses" and "rebinds" a variable? - python

From Python in a Nutshell:
Eschew global
Never use global if the function body just uses a global
variable (including mutating the object bound to that
variable, when the object is mutable).
Use a global statement only if
the function body rebinds a global variable (generally by assigning to the variable’s name).
What are the meanings and differences between "uses" and "rebinds" a variable?
Is "mutating the object bound to a variable" "uses" or "rebinds" the variable? Why?

"Mutate" and "bind"/"rebind" are two mutually exclusive operations. Mutating changes an object, whereas binding changes a name.
This is binding:
a = []
This is mutating:
a.append(None)
"Using" means accessing the existing object bound to a name, whether for reading or for mutation.

Using a variable
When you use a variable, you are using the actually value of the variable - the object to which it refers - or mutating the object that the variable name refers to. Here is an example:
>>> var1 = 1
>>> var2 = [1]
>>>
>>> def func():
print(var1)
var2.append(2)
>>> func()
1
>>> var2
[1, 2]
>>>
In the above example, we are using var1 and var2 inside of func. We use var1 because we use it's value in our call to print. And we used var2 because we mutated the object it referred to. Notice we didn't change the object to which var2 referred, we used the already existing object and modified it. Also notice that we never tried to assign a new value to either variable.
Rebinding a variable
When you rebind a variable, you are changing the object to which the variable name refers. Here is another example to help illustrate the point:
>>> var1 = 1
>>>
>>> def func():
global var1
var1 = 2
>>> func()
>>> var1
2
>>>
In the above examples. We are rebinding var inside of func. var1 use to have a reference to the object 1, but because we rebinding var1 to 2, it now refers to the object 2.
So what's the difference?
The difference is that when we use a variable, we are simply using the object to which the variable already refers. When we rebind a variable, we change the object to which the variable refers.

Related

Python: How can a closure continue its existence after the outer function is reassigned?

I learning about closures in Python, and I get the concept well enough. While messing around in IDLE I thought about what would happen if I reassigned the enclosing function and then tried to call the enclosed function:
>>> def outer_function(hello):
message = hello
def inner_function():
print(message)
return inner_function
>>> function = outer_function("hello")
>>> function()
hello
>>> def outer_function():
print("hi")
>>> function()
hello
I think that this is very interesting, but I realize that I do not have a good enough understanding about what happens to the closure in memory etc. Can someone explain how I can call inner_function after reassignment of outer_function?
In CPython (i.e. the reference implementation written in C that most people think of as just "Python"), lexical closures are implemented as 'flat closures' (see PEP 227) that use cell object references instead of searching a linked list of frame objects (nested scopes) at runtime. This allows for quick lookup and improves garbage collection when returning a closure function.
The bytecode in outer_function is specialized to access a cell object in the stack frame instead of directly referencing the message object. The interpreter knows about this when setting up the stack frame for the call because the code object has this variable defined as a cell variable:
>>> outer_function.__code__.co_cellvars
('message',)
The bytecode in inner_function also dereferences a cell object for the value of message, but since it's not the source of the object, it's classified instead as a free variable:
>>> type(outer_function.__code__.co_consts[1])
<class 'code'>
>>> outer_function.__code__.co_consts[1].co_name
'inner_function'
>>> outer_function.__code__.co_consts[1].co_freevars
('message',)
Each inner_function function object that gets instantiated has a __closure__ tuple that references the cell for the enclosed free variable. For example:
>>> function = outer_function('hello')
>>> type(function.__closure__[0])
<class 'cell'>
>>> function.__closure__[0].cell_contents
'hello'
The cells in this __closure__ tuple are loaded in the stack frame when function is called.
This tuple of cells is what makes it flattened. No matter how deeply you nest the scopes, the __closure__ will always propagate all of the required cells. For example:
def a():
x = 1
def b():
def c():
def d():
x
print('d.__closure__[0].cell_contents:',
d.__closure__[0].cell_contents)
print('c.__closure__[0].cell_contents:',
c.__closure__[0].cell_contents)
c()
print('b.__closure__[0].cell_contents:',
b.__closure__[0].cell_contents)
b()
>>> a()
b.__closure__[0].cell_contents: 1
c.__closure__[0].cell_contents: 1
d.__closure__[0].cell_contents: 1
Functions b and c don't directly reference x, but they have to propagate the cell for the inner function d to reference it.
The above inspection relies on CPython implementation details. In Python 3.3+ you can instead call inspect.getclosurevars to inspect closure variables. For example:
import inspect
def outer_function(hello):
message = hello
def inner_function():
print(message)
return inner_function
>>> function = outer_function('hello')
>>> inspect.getclosurevars(function)
ClosureVars(nonlocals={'message': 'hello'},
globals={},
builtins={'print': <built-in function print>},
unbound=set())
This behavior is not limited to closures. What you just did is create a copy of the whole function object which refers to the older function and obviously it persists even if you create another function of the same name.
def test(x): return 2*x
t = test
# redeclare the function
def test(x): return 3*x
Tests
>>> test(2)
6
>>> t(2)
4
>>> t is test
False
Also, you can check the locations of both t and test which shows they are different objects -
>>> t
<function test at 0x10f9d6d90>
>>> test
<function test at 0x10f778f28>
You can see they are both the same functions but at different locations and hence different objects. The same thing is happening in your case.

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.

Python: modify global var from a function argument [duplicate]

This question already has answers here:
How do I create variable variables?
(17 answers)
How do I get a result (output) from a function? How can I use the result later?
(4 answers)
Closed 7 months ago.
I'd like to create a function that will modify an initialized global variable based on the argument passed to it, but I get a SyntaxError: name 'arg' is local and global. I have seen other methods to accomplish this, using globals() or creating a simple func inside myFunc to "trick" Python. Another approach would be to create if statements inside myFunc to explicitly assign the corresponding global variables, but that seems overly verbose.
Why does this occur, and what would be the most efficient/elegant/Pythonic way to accomplish this?
Given:
var1 = 1
var2 = 2
var3 = 3
def myFunc(arg):
global arg
arg = 10
myFunc(var1) # each of these should print to 10
myFunc(var2)
myFunc(var3)
You can use globals() to access the variables and assign new values from within myFunc()
var1 = 1
var2 = 2
def myFunc(varname):
globals()[varname] = 10
print(var1, var2)
myFunc("var1")
myFunc("var2")
print(var1, var2)
Will output:
1, 2
10, 10
In python a variable is a name for an object. When you call a function, and pass it an argument you're passing the object associated with the variable, not the name. So for example when you call wash(mydog), you're saying "wash the object known as mydog". Keep in mind, that the same object could have more than one name, for example spot = mydog = best_dog_ever = new_dog(). The function doesn't know which name was used to pass it the object, and even if it did, what if the name used was not the one in the global scope, you'd have to have some way of saying this function only takes global variables as arguments.
I hope that helps explain why you're getting a syntax error, but you can still accomplish pretty much the same thing at least two ways. The first is to simply assign the return value to the variable you're trying to change, for example:
var1 = 1
var2 = 2
def addone(a):
return a + 1
def main():
global var1, var2
var1 = addone(var1)
var2 = addone(var2)
print var1, var2
main()
print var1, var2
The second is to use a more object oriented approach, something like this:
class GlobalValue(object):
def __init__(self, value):
self.value = value
var1 = GlobalValue(1)
var2 = GlobalValue(2)
def addone(a):
a.value += 1
print var1.value, var2.value
addone(var1)
addone(var2)
print var1.value, var2.value
Or even better:
class GlobalValue(object):
def __init__(self, value):
self.value = value
def addone(self):
self.value += 1
var1 = GlobalValue(1)
var2 = GlobalValue(2)
print var1.value, var2.value
var1.addone()
var2.addone()
print var1.value, var2.value
Why does this occur
Because the global variable that you want to use has the same name as the parameter, arg. In Python, parameters are local variables, and a variable can only be local or global, not both.
It appears as though you expected to be able to use the contents of var to, somehow, specify which existing global variable to modify. It does not work like that. First off, variables don't contain other variables; they contain values. The name of a variable isn't a value. Again, parameters are local variables - and calling a function assigns to those variables. It assigns a value. (Keep in mind that you could just as easily call the function without a variable for the argument: myFunc(3). Should this cause 3 to become equal to 10 somehow?)
Second, even if you passed, for example, a string, you would have to do more work in order to access the corresponding global variable. It can be done, but please do not.
and what would be the most efficient/elegant/Pythonic way to accomplish this?
The Pythonic way is don't. If you want your function to communicate information out when it is called, return a value:
def myFunc():
return 10
var1 = myFunc()
var2 = myFunc()
var3 = myFunc()
The simplest way to fix the error is to just rename the global variable. However, this does not fix the apparent intent of the code:
var1 = 1
var2 = 2
var3 = 3
def myFunc(arg):
global g
g = 10
# var1, var2 and var3 will NOT CHANGE
# Instead, the new global variable g is created, and assigned a value of 10,
# three times in a row.
myFunc(var1)
myFunc(var2)
myFunc(var3)

What does list[:] = process_list(list) does in python? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
What is the difference between list and list[:] in python?
I am quite new in python so I bumped into a situation where I was unable to find a response for the following question.
What does this means in python?
l[:] = process_list(l)
l is type list
Basically I have a global declared list that I want to modify it(override the old values with the new ones) based on the response of the process_list method. When I try like this:
l = process_list(l)
I get this: Unresolved reference 'l'
Can you please explain what is the difference and if the first approach that I am using currently is a good one?
In a function, an assignment to a name that could be a local variable creates a local variable by that name, even if it shadows a global:
a = None
def foo():
a = 5 # local 'a' shadows global 'a'
Slice assignment is modification, not assignment, so the name continues to refer to the global:
a = [1, 2, 3]
def foo():
a[:] = [5] # modifies global 'a'
The Unresolved reference happens because by creating a local variable shadowing the global, the global can no longer be seen. Another way to do what you want could be to use global:
a = None
def foo():
global a
a = 5 # rebinds global 'a'
list[:] = whatever will change the contents of the existing list
(as opposed to replacing it with list = whatever) ... by the way list is a terrible variable name ...

Python and closed variables [duplicate]

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.

Categories

Resources