Why are only mutable variables accessible in nested functions? - python

here is a simple (useless) function:
def f(x):
b = [x]
def g(a):
b[0] -= 1
return a - b[0]
return g
it works fine. let's change it a tiny bit:
def f(x):
b = x
def g(a):
b -= 1
return a - b
return g
Now it gives an error saying that b is undefined! Sure, it can be solved using nonlocal, but I'd like to know why does this happen in the first place? Why are mutables accessable and immutables aren't?

Technically, it has nothing to do with mutable or immutable (the language does not know whether a type is "mutable" or not). The difference here is because you are assigning to the variable in one case and just reading from it in the other case.
In your second example, the line b -= 1 is the same as b = b - 1. The fact you are assigning to the variable b makes a local variable named b, separate from the outside variable named b. Since the local variable b has not been assigned to when evaluating the right side of the assignment, reading from the local b there is an error.
In your first example, the line b[0] -= 1 is the same as b[0] = b[0] - 1. But that is not a variable assignment. That is just a list element access. It is just syntactic sugar for b.__setitem__(0, b.__getitem__(0) - 1). You are not assigning to the variable b here -- all you're doing is reading from the variable b two times (in order to call methods on the object it points to). Since you are only reading from b, it uses the external variable b.
If in the first example, with your mutable list, you did a variable assignment like you did in the second example, it would equally create a local variable, and the reading of that local variable before assignment would also not work:
def f(x):
b = [x]
def g(a):
b = [b[0] - 1]
return a - b[0]
return g

Related

Mutable and immutable objects show different behviours in functions wit python

Why mutable objects like array or list can be visited and changed in function directly while immutable objects like number can be only visited but not changed? What is the mechnism. Below is some simple test code.
import numpy as np
a = [1,2,3]
b = np.array([1,2,3])
c = 3
def func():
a.append(1)
b[0] = 2
c += 1
print(c)
func()
The difference is whether you assign or mutate. When you mutate data, like with a.append(1), the object reference (a) is not changed: it is still the same list reference. When you assign, the variable really gets a different reference, and the object that was previously referenced does not get affected.
Without global, globals can be mutated (when they are mutable), but not assigned.
This has little to do with mutable or not, as even a = [] would not be allowed without the corresponding global statement. Even though lists are mutable, this a = [] is not attempting to mutate anything. It assigns. And that requires global.
Well, you're kind of answering your own question.
Mutable objects are internally mutable, while immutable objects, well, aren't.
The global c (or nonlocal c in an inner function) statement tells Python that the name c refers to the name c in the global (or outer) scope.
Since integers aren't internally mutable (and don't implement __iadd__, the magic method that could back +=), c += 1 does c = c + 1, i.e. assigns the value of the expression c + 1 to the global name c.
If you attempt c += 1 in a function without the global statement, you'll get an UnboundLocalError, since there is no name c to access and increment before that assignment itself.
This stands for any name; trying to do
>>> a = []
>>> def f():
... a = a + ["foo"]
...
>>> f()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in f
UnboundLocalError: local variable 'a' referenced before assignment
also fails, since a has not been assigned within the function.

Difference in transmitting variables in an inner function in Python

In Python, is there a difference between the following two codes both of which gives the same result c = [0,-10]?
def foo1():
a = [0,1]
def foo2():
a[1] = -10
foo2()
return a
c = foo1()
and
def foo1():
a = [0,1]
def foo2(b):
b[1] = -10
foo2(a)
return a
c = foo1()
Some commentator suggests this is answered by this question. But it does not since my question asks about the passing of variables through an inner function whilst the linked question does not.
In the first, a is a free variable whose value is taken from the nearest enclosing scope (in this case, the scope of foo1) that defines a.
In the second, b is a local variable initialized using the argument passed to foo2 when it is called, which is the variable a defined in foo1.
In each case, you assign -10 to the second "slot" of the same list.

Passing parameter to inner function

I have a function with inner function. I wonder what is a proper way to pass variables to inner function. From what I can see tables are passed by default, although I am not sure whether this is a undocumented workaround or python design.
def function():
def inner_function():
if a[0] > b[0]:
print("a[0] = {0}, b[0] = {1}".format(a[0], b[0]))
tmp = c
c = d
d = tmp
a = [4, 3]
b = [2, 1]
c = 1
d = 2
inner_function()
function()
python test.py output:
$ python test.py a[0] = 4, b[0] = 2 Traceback (most recent call last):
File "test.py", line 16, in
function()
File "test.py", line 14, in function
inner_function()
File "test.py", line 5, in inner_function
tmp = c
UnboundLocalError: local variable 'c' referenced before assignment
What is a proper way to pass variables from "function" to "inner_function"? Is there any other way than by parameter? Why there is an error on "c" variable reference and not on "a" table?
AFAIK c is throwing an error because is assigned inside inner_function so it is a different variable than the c variable defined in function. Variables a and b work because they are only read at inner_function so they are not being redefined. Renaming c and d to new_c and new_d make it works.
https://pyfiddle.io/fiddle/184e1778-adb7-4759-8951-da699751c31e/
More info about Python nested functions variable scoping
def function():
def inner_function():
if a[0] > b[0]:
print("a[0] = {0}, b[0] = {1}".format(a[0], b[0]))
tmp = c
new_c = d
new_d = tmp
a = [4, 3]
b = [2, 1]
c = 1
d = 2
inner_function()
function()
you need to state c and d variables as global (by the way this is not a good idea )
def function():
global c,d
a = [4, 3]
b = [2, 1]
c = 1
d = 2
def inner_function():
if a[0] > b[0]:
global c,d
print("a[0] = {0}, b[0] = {1}".format(a[0], b[0]))
tmp = c
c = d
d = tmp
inner_function()
Although your question has been answered I am not sure it is clear why your code produce this error.
Anyway to make it clear the line that causes problem is c = d although your interpreter disagrees.
Let me explain a bit. You are inside inner_function() and without these lines (c = d and d = tmp) you are referring to variables a, b, c and d assigned outside of inner_function(). So, implicitly you are referring to these variable (a, b, c and d that is) as global.
The moment you assign a value to a variable inside an inner function then the interpreter consider it to be local variable of this function. In this case c is considered local now (since there is an assignment inside inner_function() even after the statement the interpreter complains about tmp = c). So, normally the interpreter complains about a variable it has not been assigned a value but it's being accessed anyway.
So to make it clear, python does not distinguish between type of variable (as for example java does).
What is a proper way to pass variables from "function" to
"inner_function"?
Use parameters is the proper way.
Is there any other way than by parameter?
As mentioned by others you can use global variables but it's not recommended as approach.
Why there is an error on "c" variable reference and not on "a" table?
As I mentioned before there is no distinction between different types of variables. What make them different is that you are assigning in variable c a value but just accessing the value of a for example in the other case.
This specific answer (also mentioned by #raul.vila) provides a good explanation.
Finally, since it's not clear what you are trying to achieve here. There is a difference if you are trying to print a global (even implicit) variable in an inner function or you are trying to change the value of a global variable for inside an inner function.
I guess a Pythonic way definitely refers to a duck and a rabbit, possibly even a knight. I'd also second #Metareven on passing them in as arguments as Python has a very succinct way of handling them. This way you do not need to worry about #global variables. And you have a good idea about what goes in and as suggested what comes out.
def function(duck):
def inner_function(rabbit):
if rabbit[0][0] > rabbit[1][0]:
print("a[0] aka the first one behind the rabbit = {0}, \nb[0] aka the second one behind the rabbit = {1}".format(rabbit[0], rabbit[1]))
tmp = rabbit[2]
rabbit[2] = rabbit[3]
rabbit[3] = tmp
inner_function(duck)
#Let's sort out the arguments
a = [4, 3]
b = [2, 1]
c = 1
d = 2
function([a,b,c,d])
The function call returns the following:
python innner.py
a[0] aka the first one behind the rabbit = [4, 3],
b[0] aka the second one behind the rabbit = [2, 1]
Has this answered your question?

How to call on a variable which has been defined in a previous function?

My code is as follows...
def addition(a, b):
c = a + b
return c
And I then want to be able to use C later on in the program as a variable. For example...
d = c * 3
However, I get a NameError that 'C' is not defined... But I have returned c, so why can I not use it later on in the code?! So confused. Thanks!
(This is obviously a simpler version of what I want to do but thought I'd keep it simple so I can understand the basics of why I cannot call on this variable outside my function even though I am returning the variable. Thanks)
You have returned the value of c but not the whole variable i.e. the name c exists only within the scope it is instantiated.
So, if you want to use the value returned, you should re-assign it to a new name. You can do it by re-assigning it to c again, but it could be any name you wanted.
def addition(a, b):
c = a + b
return c
new_var = addition(1,2) #new_var gets the value 3
c = addition(2,3) #c gets the value 5
Take a look at this nice explanation about variables and scopes (link)
You usually define a function to use it later in your code. For that case, use another global variable c:
def addition(a, b):
c = a + b
return c
c = addition(1, 2)
d = c * 3 # d == 9
Functions allow this usage of repeated code, or procedure distinction, so that you can later write in your code
m = addition(4, 5)
and it will store the required result of the functionality into m.
If you want to define c in the function and use it later, you can use global variables.
c = 0
def addition(a, b):
global c
c = a + b
return c
It's not considered good to use globals, though. You could also call the function in the variable assignment.
d = addition(a, b) * 3
For this, you need to put real numbers in the place of a and b. I recommend you use the second option.

What is happening in this Python program?

I'd like to know what is getting assigned to what in line 8.
# Iterators
class Fibs:
def __init__(self):
self.a = 0
self.b = 1
def next(self):
self.a, self.b = self.b, self.a+self.b # <--- here
return self.a
def __iter__(self):
return self
fibs = Fibs()
for f in fibs:
if f > 1000:
print f
break
The rest of the program I really don't need much explanation. I'm not sure what's getting assigned to what.
It's a multiple assignment roughly equivalent to this:
tmp = self.a
self.a = self.b
self.b = tmp + self.b
Or this pseudo-code:
a' = b
b' = a + b
As you can see the multiple assignment is much more concise than separate assignments and more closely resembles the pseudo-code example.
Almost that example is given in the Python documentation as an example of calculating Fibonacci numbers. One of the advantages of the multiple assignment is that the variables on the right hand side are evaluated before any of the assignments take place, saving the need for the temporary variable in this case.
It's a pair assignment, a shorthand of
t = self.a
self.a = self.b
self.b = t+self.b
just to use an one-liner instead that two assignments.. to be precise i think that the left operand of the assignment is considered a tuple of two elements, so you are like assigning to tuple (self.a, self,b) the value (self.b, self.a+self.b) which does the same thing as the three separate assignments written before without the need of a temporary variable. This because, while without using tuple the assignments are executed sequentially, in your example they are resolved at the same time, preserving the value of self.a in second assignment.
As stated in documentation:
Assignment of an object to a target list is recursively defined as follows.
If the target list is a single target: The object is assigned to that target.
If the target list is a comma-separated list of targets (your case): The object must be an iterable with the same number of items as there are targets in the target list, and the items are assigned, from left to right, to the corresponding targets. (This rule is relaxed as of Python 1.5; in earlier versions, the object had to be a tuple. Since strings are sequences, an assignment like a, b = "xy" is now legal as long as the string has the right length.)
Without looking at the surrounding code, I'd say it's the heart of an algorithm for computing Fibonacci numbers.
It translates to the equivalent of:
a = b
b = a + b
...thereby computing the next number(s) in the sequence.
If you look at a sequence of numbers like
1 1 2 3 5 8 13 21 ...
and you let a and b be the last two numbers, then afterwards you'll have the next number in b and the former last number (b) in a.
The reason to use that strange notation is so as to accomplish both assignments at the same time. If they were done sequentially as in the 2 lines above, the value of a would be clobbered in the first line and you'd just be doubling b in the 2nd line.
Be aware that a paired assignment is not a "special feature" of Python. If you know a bit about Python, it's something you already know about but you may not know you know. When you put the following into the python console:
>>> 'a', 'b'
What you get in return is:
('a', 'b')
In other words, a tuple. In your example,
self.a, self.b = self.b, self.a+self.b
what you're really doing is:
(self.a, self.b) = (self.b, self.a+self.b)
Create a tuple that contains the value of self.b and the value of self.a+self.b. (The tuple on the right.)
Create a tuple that contains self.a and self.b. (The left-hand tuple.)
In order to create that left-hand tuple, create a new instance of self.a and self.b for that new tuple. Their old values don't matter anymore: they're in the temporary right-hand tuple.
Assign value 0 of the left tuple variable to value 0 of the right tuple.
Assign value 1 of the left tuple variable to value 1 of the right tuple.
Now that both variables of the left tuple are assigned, delete both tuples. The new variables remain with their new values.
So, for example, you can do:
>>> a, b = 1, 2
>>> a, b
(1, 2)
>>> a, b = b, a
>>> a, b
(2, 1)
There are still temporary variables involved under the hood, but you, the programmer, don't have to deal with them.

Categories