What is the scope of argument variables in python? - python

Consider the following cases:
Case 1:
def fun(arg):
arg += 1
my_var = 1
fun(my_var)
print(my_var)
>> 1
Case 2:
def fun(arg):
arg += [4]
my_var = [1,2,3]
fun(my_var)
print(my_var)
>> [1, 2, 3, 4]
Case 3:
def fun(arg):
arg = arg + [4]
my_var = [1,2,3]
fun(my_var)
print(my_var)
>> [1, 2, 3]
Case 4:
def fun(arg):
print(arg)
fun("Hi")
print(arg)
>> Hi
Traceback (most recent call last):
File "<string>", line 8, in <module>
NameError: name 'arg' is not defined
Case 4 demonstrates that the scope of the argument variable lies within the function. Case 1 and 3 support that as changes to the arg variable within the function are not reflected in the parameters globally.
But why does Case 2 happen at all? I noticed the same thing when I used append instead of the +=. Shouldn't the changes that happen to arg not have any effect on the variables the function was called with from a different scope?
Any help appreciated. Thanks in advance.

The answer is simple: Function arguments are local variables!
Some more observations:
= is the assignment operator and rebinds a variable. This will never mutate objects (ecxeption slice assignment).
+= is its own operator and is implemented as a mutation for mutable types. You can implement its behaviour for your own types by implementing the __iadd__ dunder method.
Hence, in general, a = a + b is not equivalent to a += b!
For lists, a += b corresponds to a.extend(b). a + b on the other hand, creates a new list object, and a = a + b rebinds variable a to that new object.

Case 2 is the only one where you use a mutating method on an instance. This affects the parameter passed.
The others do nothing or just reassign the argument. Assignment in python only affects the variable being assigned and not other variables that still refer to the previous instance.
Mandatory link to Ned Batchelder

Python lets you create variables simply by assigning a value to the variable, without the need to declare the variable upfront. The value assigned to a variable determines the variable type.
if you print the variable type on each function you will see it.
#case 2
def fun2(arg2):
arg2 += [4]
my_var2 = [1,2,3]
#fun(my_var2)
print(type(my_var2))
print(type(my_var2[0]))
#case 4 where no type assigned
def fun4(arg44):
print(arg)
#fun(myvar4)
print(arg4)
Watch this code in action with a visual explanation.

Any variables declared inside a function are local, but lists and dict behave a bit differently. You can append elements to a list even if you dont write global list_name
inside the function nor pass the lists as arguments to the functon. Only lists and dict behave this way. However the difference in case 2 and 3 seems to be a bug. It seems that when you write
a = [1,2,3]
def fun(a_list):
a_list = a_list + [4]
print(a_list)
fun(a)
>>>[1,2,3,4]
you get [1,2,3,4], so it becomes local variable, but when you write arg += [4] , it behaves as global variable

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.

Nested Function Scope [duplicate]

This question already has answers here:
UnboundLocalError with nested function scopes [duplicate]
(3 answers)
Closed 2 years ago.
def foo():
a = 0
def bar():
a += 1
bar()
foo()
Why is it that this code does not execute when a is an immutable type, but does execute when it is mutable (a list, for example)?
I don't think the other answers narrate the entire story, so here's my 2 cents.The scope does not differ between types. That's not what's happening here. Rebinding a name, regardless of what that name refers to, will always cause an unbound local error to occur.
Some types in Python are mutable (once created, a value can be changed), and list is one of them. Some are immutable, and changing a value of these types requires creating a new object.
So, with the first example,
def foo():
a = 0
def bar():
a += 1
bar()
foo()
This doesn't work because you effectively have an assignment taking place here, a = a + 1. You can use non-local to make the above work, that being besides the point.
Doing the same thing with a list:
def foo():
a = []
def bar():
a.append(1)
bar()
foo()
This does indeed work. There is no assignment taking place here.
You can't re-bind the name to a different object, but if the object is mutable, you can modify its contents.
Now, there's 2 more cases you should be aware of.
def foo():
a = []
c = []
def bar():
a = c + [1]
bar()
print(a)
print(c)
foo()
This will work, however you should note that the a inside bar() now is local to bar, and the print() statement should reflect that.
But here's a gotcha
def foo():
a = []
def bar():
a = a + [1] #or even a += [1], doesn't matter
bar()
print(a)
foo()
This won't work! (And it's important you contrast this snippet with the first snippet, because that addresses why this has nothing to do with scopes. Take a minute to read it again.)
So this doesn't work and it's important to understand it.
If there is an assignment to a variable inside a function, that variable is considered local. Now, in the last case, when we did a = c + 1, it was more like a_local = c_nonlocal + 1.
In this case, a = a + 1 is a_local = a_local + 1 and hence, that will indeed cause an error. This is why, Rebinding a name, regardless of what that name refers to, will always cause an unbound local error to occur. In the case earlier(The third snippet), it wasn't rebinding it - it was creating a local variable. In the latter case(The fourth snippet), it was infact rebinding and hence the error.
There is a keyword nonlocal which is used to access nonlocal variables.
The nonlocal keyword is used to work with variables inside nested functions, where the variable should not belong to the inner function. --w3schools.com
def foo():
a = 0
def bar():
nonlocal a
a += 1
bar()
foo()
Thank you
That is because you are not returning any value from either functions and the second a is only local to the function bar().
Try using the nonlocal keyword like this:
def foo():
a = 0
def bar():
nonlocal a
a += 1
return a
return bar()
print(foo())
Output
1

nested function change variable in an outside function not working [duplicate]

This question already has answers here:
UnboundLocalError trying to use a variable (supposed to be global) that is (re)assigned (even after first use)
(14 answers)
Closed 6 months ago.
def some_func(a):
def access_a():
print(a)
access_a()
outputs the value of a. However, if I want to change a in the nested function like this:
def some_func(a):
def change_a():
a += 1
print(a)
change_a()
it raises UnboundLocalError exception.
I know a is a nonlocal variable, but why can I access it without declaring nonlocal a?
Python scoping rules 101:
a name bound in a function body is considered local unless explicitely declared global (Python 2.x and 3.x) or nonlocal (Python 3.x only). This holds true whereever the assignment happens in the function's body. Trying to read a local variable before it's bound is of course an error.
if a name is read but not bound in a function's body, it will be looked up in enclosing scopes (outer function(s) if any then global scope). NB: functions arguments are de facto local names so they will never be looked up in enclosing scopes.
Note that a += 1 is mainly a shortcut for a = a + 1, so in your example a is local (bound in the function's body and not explicitely declared global or nonlocal), but you try to read it (the rhs of a = a+1) before it's bound.
In Python 3 you can solve this with a nonlocal statement:
>>> def outer(a):
... def change():
... nonlocal a
... a += 1
... print("before : {}".format(a))
... change()
... print ("after : {}".format(a))
...
>>> outer(42)
before : 42
after : 43
Python 2 doesn't have nonlocal so the canonical hack is to wrap the variable in a mutable container (typically a list but any mutable object would do):
>>> def outer(a):
... _a = [a]
... def change():
... _a[0] += 1
... print("before : {}".format(_a[0]))
... change()
... print ("after : {}".format(_a[0]))
...
>>> outer(42)
before : 42
after : 43
which is quite ugly to say the least.
Now while closures are quite handy, they are mostly the functional counterpart of objects : a way to share state between a set of functions while preserving encapsulation of this state, so if you find you have a need for a nonlocal variable perhaps a proper class might be a cleaner solution (though possibly not for your example that doesn't return the inner function but only uses it internally).
i have two solutions for you:
#first one:
# try with list, compound data types dict/list
def some_func(a):
def change_a():
a[0] += 1
print(a[0])
change_a()
some_func([1])
>>> 2
#second one
#reference pointer
from ctypes import *
def some_func_ctypes(a):
def change_a():
a[0] += 1
print a.contents, a[0]
change_a()
i = c_int(1)
pi = pointer(i)
some_func_ctypes(pi)
>>> c_int(2) 2
When you use the += operator, there is an assignment of a new value to a. This turns a to a local in the eyes of the interpreter.

Modify global list inside a function

First of all, I understand that I can use global statement to access global variables. But somehow I was able to modify a global list without global like below:
def func1(nums):
nums = [4,5,6]
nums = [1,2,3]
func1(nums)
print nums # print [1,2,3]
def func2(nums):
nums[0] = 4
nums[1] = 5
nums[2] = 6
nums = [1,2,3]
func2(nums)
print nums # print [4,5,6]
After trying func2, I realized that I can always access global list in a function if I specify the index:
def func3(nums):
nums[:] = [4,5,6]
nums = [1,2,3]
func3(nums)
print nums # print [4,5,6]
Is it because Python automatically goes trying to match a global variable if a function variable is used before definition?
I understand that I can use global statement to access global variables
Your understanding is wrong. You can always access a global variable as long as you don't have a local variable of the same name. You only need the global statement when you are going to change what object a variable name refers to. In your func2, you are not doing that; you are only changing the contents of the object. nums still refers to the same list.
It is of concept based on mutable and immutable objects in Python. In your case, for example:
a=[1,2]
def myfn():
a=[3,4]
print id(a)
>>>id(a)
3065250924L
>>>myfn()
3055359596
It is clear both are different objects. Now:
a=[1,2]
def myfn():
a[:] =[3,4]
print id(a)
>>>id(a)
3055358572
>>>myfn()
3055358572
That means it is same variable using in local and global scope.
In this specific case it is because lists are mutable.
As a result having them in the global namespace, or even passed through a function, means that they will be changed as Python holds the reference to the mutable object, not a copy of it.
If you try the same thing with tuples it will not work, as they are immutable.
The way to avoid this is to provide a copy of the list to the function, not the list itself:
func2(list[:])
At the same time you can do this with default arguments, where you can specify a default argument to be [], and if you then .append() something to it, that default argument will forever hold that item within it for all future calls (unless you remove it in some way).
2 variables nums are different and they point to a same object or 2 different objects, though they have same name.
when you call func1(nums), mean that you pass a reference. Now the 2 variable nums point to same object. (2 variables, 1 object)
when you assign in func1, the inside variable nums will point to a new object, the outside is still unchanged (2 variables, 2 object)
and when you call print nums then this nums is the outside variable,
There are two reasons for this result:
Variables are simply names that refer to objects
List is mutable
In func1,nums refer to a new object because a new list is created. Therefore global nums is not affected.
In func2, the modification is applied to the object passed in. Thus global nums is changed. A new object is not created because list is mutable.
ref: https://docs.python.org/3/faq/programming.html#what-are-the-rules-for-local-and-global-variables-in-python

Why wont python return this variable, how can i get it to work?

When I run the following program it prints "2" and then "1" when i want it to print "2" and then "2". What have i done wrong?
def thingy(a):
a= a + 1
print(a)
return a
a=1
thingy(a)
print(a)
Many thanks if you can help.
You haven't re-assigned the a variable in your global scope. The a variable in the global scope isn't the same as in the function. To be more clear, look at this:
def thingy(arg):
arg = arg + 1
print(arg)
return arg
a=1 # Assign a to 1
thingy(a) # Do some work without altering variable
print(a) # Print a, which is 1...
a = thingy(a) # Here, a will be equals to two now.
With global option
You can also edit the global scope by adding global a at the beginning of the function and don't pass any argument to the function:
def thingy():
global a
a= a + 1
print(a)
return a
a=1
thingy()
print(a)
But prefer the first method, more cleaner.
You have remember that variables in functions are only in scope inside the function. So when you create this function:
def thingy(a):
a= a + 1
print(a)
return a
a is only in scope inside the function. No changes made to it exist outside the function. To use the returned value of a function you need to set a variable to be equal to the result of the function. So the a in the function and the a outside the function are not the same variable. Example:
def foo(a):
a = 2
return a
a = 1 #set a = 1
foo(a) #use a in function
a will still be one since the a inside the function is now out of scope.
a = 1
a = thingy(a)
print(a)
although it may look like you're setting a to 2 in the method, the scope is completely different. Imagine if you'd named the variable in the method b instead. They're different variables with the same name because of the scope.
Being a number your 'a' variable that you pass to thingy function is immutable and thus all manipulations inside your functions will not be passed back to original a. Though if you pass a as a list and change it in the function then you'll see all the changes you've made to the list after returning from a function.
def thingy(a):
a[0]= a[0] + 1
print(a[0])
a=[0]
thingy(a)
print(a[0])
you are mistaken with the a's.
functions live in their own world. they use vars of themselves. unless you tell them to use a global var.
you have lots of options to fix this:
use "global" to tell the function to use the global a.
def thingy(a):
global a;
a= a + 1
print(a)
return a
just by adding the line "global a" you tell the function "hey, you are not alone - use everyone's a".
the problem is that in your script, you use a as a global variable, and as the argument the function take. instead, write:
def thingy():
because you don't need the argument anymore.
so your code will look like this:
def thingy():
global a;
a= a + 1
print(a) #You don't need the "return a", you don't use it.
a=1
thingy()
print(a)
Use the return.
instead of using a global var, you can set "a" to whatever the function returns.
so instead of just calling thingy(a), use:
a = thingy(a)
I will use variable called "b" inside the function.
def thingy(b):
b = b + 1
print(b)
return b
a = 1
a = thingy(a)
print(a)

Categories