global variable with setdefault in python - python

I'm a bit confused about the behavior of global variables with the use if setdefault() in python:
Please find sample code for how this are referenced/resolved using setdefault, can somebody help me clarify what is going on in here?
Main file where variables are resoved :
#main
from test import *
fill_func()
print my_list
print my_dict
Test file where variables are assigned values :
#test
my_list = []
my_dict = {}
def fill_func():
my_list = [1,2,3]
print my_list
my_dict.setdefault(0,[]).append('zero')
print my_dict
Output :
[1, 2, 3]
{0: ['zero']}
[]
{0: ['zero']}
I'm not able to understand why the list(my_list) variable shows empty when called from main.py, whereas my_dict shows data fine?
Any help is appreciated. TIA!
#
Sample test file 2
#test
my_list = []
my_dict = {}
def fill_func():
def fill_list():
global my_list
my_list = [1,2,3]
print my_list
my_dict.setdefault(0,[]).append('zero')
print my_dict
fill_list()
Ouput :
{0: ['zero']}
[1, 2, 3]
[]
{0: ['zero']}
Can someone please throw some light on the second test file, please bear with me, trying to understand the basics :)
TIA!

my_list is defined as a local variable inside the fill_func; this is shadowing the other my_list defined in the global scope.
therefore, your code first calls the fill_func that prints the local my_list, then the default dict. It then exits the function and prints the outer scope my_list, and the dict (which was not shadowed)

You are creating a local variable with the same name as the global variable. Just add global my_list at the top of the function.

That's because, as you've rightly indicated, it has to do with scope. my_list and my_dict are global to test.py, which have to accessed using the global qualifier. That is, your code should be:
# Edited to address comments (see explanation below)
def fill_func():
global my_list # this is necessary since you're updating the values
global my_dict
my_list.extend([1,2,3])
print my_list
my_dict.setdefault(0,[]).append('zero')
print my_dict
EDIT:
To get the both the list and the dictionary to update, one has to extend the list, and modify the dictionary (as you've done) - i.e. actually change its value. Assigning it a value using the assignment operator only changes what the variable refers to and not the value itself. This is why it doesn't update outside of the local function scope. And, this is also why it updates when we use other methods to modify the contents of those variables.
The problem is when a function body is parsed all the variables used
in either normal assignments or augmented assigments are considered as
local variables, so when the function gets called Python will not look
for those variables in global scope hence it will raise an error.
Hence you need to specify those variables as global to tell Python to
look for them in global scope.
Another alternative is to use list.extend()
(From here: https://stackoverflow.com/a/23436510/866930 . An additional reference that's useful on this is: https://stackoverflow.com/a/31437415/866930)
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.
Compare to this version:
def fill_func():
global my_list
global my_dict
my_list = [1,2,3]
print my_list # prints `[1, 2, 3]` here and `[]` in main
my_dict = {1: 'a', 2: 'b'}
print my_dict # prints `{1: 'a', 2: 'b'}` here, and `{}` in main
Without the use of global, Python would think that the variable is local to the scope of the code element where it was defined, hence global tells the interpreter to look within the globals symbol table (these contents can be accessed via globals())

you need to declare in your function that you want to use the global variable.
You can do that by
def fill_func():
global my_list
global my_dict
my_list = [1,2,3]
print my_list
my_dict.setdefault(0,[]).append('zero')
print my_dict
Please note that normally it is preferred to use only CAPITAL LETTERS for globals to avoid confusion

Related

Local and Global lists in Python

Please help me as I am new to python
when I call this function the original value of list1 changes
def mystery(list1):
list1[0] , list1[1] = list1[1], list1[0]
list1 = [7,82,44,23,11]
mystery(list1)
print(list1) #prints [82, 7, 44, 23, 11]
how it can change the value of global list1
if I change my function to
def mystery(list1):
list1 = list1 + list1[2:5]
then I am getting the global value of list1 and not the updated one.
lists are mutable objects in python, so if you are passing to a function a list all the changes that you make to the list inside your function will be reflected everywhere/globally
If you pass a mutable object into a method, the method gets a reference to that same object and you can mutate it to your heart's delight, but if you rebind the reference in the method, the outer scope will know nothing about it, and after you're done, the outer reference will still point at the original object.
If you pass an immutable object to a method, you still can't rebind the outer reference, and you can't even mutate the object.[more details here]
The reason list1 is changing is because you're actually modifying the list object inside of the function.
It really has nothing todo with global / local variables, if you renamed the parameter inside your function to something else, and still passed in list1, list1 would be modified.
If you're wanting to return a new list, you need to first create a copy of the list, there's many ways to do this. list(list1) is one way. Then return the list at the end of the function.
If I understand your queston, you want to actually append some more values to the passed in list, use list1.append(...) to add to the end of the list.
And since you're modifying the list itself, it'll change in the larger scope.
But it's still not using the global scope, as you're just using a var with the same name in the local scope.
Maybe just return the list?
def mystery(list1):
return list1 + list1[2:5]
list1 = [7,82,44,23,11]
list1 = mystery(list1)
print(list1)
In python, assigning a value to a variable like a = 5 means you are creating an object in the memory for the value 5. Now a is a link to that object holding 5 (in Layman's terms). If you change a now, say a = "something else", this creates a new object for the string "something else" in somewhere else in the memory and now a is pointing to that. This is why you get to change the data type of python variables.
When you pass something to a python function, that link is the one that is passed on. So when you call mystery(list1), original link to the list1 is passed. Therefore changing an element in the list1 means you are assigning a new value to that particular element in the original list1. No matter you are inside or outside of the function mystery() in this case since you will be using the original link you created to access the element inside list1 to change it. The change happens inside the list1; list1 didn't get reassigned.
However when you do list1 = "something new" inside your function mystery(), you are creating a new variable list1 inside the function which is local to the function. You are not changing the original list1.
Passing mutable objects into a function and then modifying the object inside the function will have the same effect as modifying the object directly.
list1 = [1,2,3]
def func(list1):
list1[0] = 5
>>>list1
[5,2,3]
This is effectively same as directly running list1[0] = 5
However if you pass an immutable object into a function such as tuple, It will not support item assignement TypeError: 'tuple' object does not support item assignment. So you need to build a new immutable object and return it.
>>> tup1 = (1,2,3) # doing tup1[0] = 5 will cause TypeError
>>> tup2 = tup1 + (5,)
>>> tup2
(1, 2, 3, 5)
Put it in function,
>>> def func2(tup1):
return tup1 + (5,)
>>> func2(tup1=(1,2,3))
(1, 2, 3, 5)

Why does a function allow a mutation of a global variable without returning it?

I have a question about variable scope in Python. Why is a mutation of a variable allowed without returning the mutated variable?
def mutation(L):
L.append("x")
L = []
mutation(L)
print(L)
I would expect this to print [], as the mutation of L inside the function only affects the local scope. Why is "L" mutated even in the global scope?
Since Python is pass by object reference, when you pass something to a function it points to the same object in memory that you can manipulate. If you want a function to not modify the original list, you need to make a copy of it when passing it to the function or inside of the function itself.
Sending a copy to the function:
def mutation(L):
L.append("x")
L = []
mutation(list(L))
print(L)
# Prints out []
Making a copy inside the function:
def mutation(L):
L = list(L)
L.append("x")
L = []
mutation(L)
print(L)
# Prints out []
This is because you are actually changing the original list/object passed into the function. Note that the underlying principle is pass-by-object-reference. So changes to the passed parameter within the function will reflect outside.
If you don't want to change globally, use [:] to create a copy and this would perform operations on the copy without mutating the original list.
def mutation(L):
L = L[:]
L.append("x")
L = []
mutation(L)
print(L)
# []

Python list mutation after copying

def mutation(input_list):
list_copy = input_list[:]
list_copy[0] = 10
input_list = list_copy
# Correctly mutates
sample_list = [0,1,2]
sample_copy = sample_list[:]
sample_copy[0] = 10
sample_list = sample_copy
print(sample_list)
# Incorrectly mutates
sample_list = [0,1,2]
mutation(sample_list)
print(sample_list)
In the top snippet of code, I've made a copy of a list and modified it. I then set the original to the copy and then it works.
What confuses me is why doing this process outside of a function works but if I were to do it inside a function (the 2nd snippet of code), it fails?
For reference, the code returns:
[10, 1, 2]
[0, 1, 2]
EDIT: I know that calling input_list[0] = 10 works. I just want to know what makes this different from what I showed above all in memory?
In mutation, input_list starts out pointing at the same object as sample_list, but later you make it point at list_copy.
sample_list is not modified. It is still pointing at the original object.
When you do it outside of the function you change sample_list to point to the new object before printing it.
I think that using the built-in function id to show the object ID will help here. If the ID of two variable names gives the same result then they refer to the same object; otherwise the objects are different.
>>> def mutation(input_list):
... print(id(input_list))
... list_copy = input_list[:]
... print(id(list_copy))
... input_list = list_copy
... print(id(input_list))
...
>>> a = list(range(10))
>>> print(id(a))
140737233394376
>>> mutation(a)
140737233394376
140737233289160
140737233289160
In the above, we see that after input_list = list_copy, the name input_list refers to identically the same object in memory as list_copy, which means it no longer refers to the list given as the function argument. This is why the mutation you expect does not work - you are modifying an entirely different object.
That's because you sets new value for input_list which is local variable of mutation function.
The simplest solution is changing value of first element of list passed as argument:
def mutation(input_list):
input_list[0] = 10
Otherwise you can write function which changes value of global variable called sample_list
def mutation():
global sample_list
list_copy = sample_list[:]
list_copy[0] = 10
sample_list = list_copy

Passing List Comprehension to a function in Python

I have a function in which I would like to pass list comprehension to as an input. I'm getting an error about my_list not being defined. I know I could put my_list outside the function, but in reality that list is generated as part of the function.
The actual definition is complex so here is simplified example:
def my_def(list_comp):
global my_list
my_list = [[1,2],[3,4]]
list_comp_output = list_comp
return list_comp_output
print my_def([x[1] for x in my_list])
Actually all we have in Python is runtime; there is no such thing as a separate compile time1.(in the scope of interpreter). and the functions are not exception from this rule.
So what you have done here is defining my_list as a global variable and attempt to use it in a list comprehension, when python doesn't defined such thing.
You can just run your function 1 time then use that list comprehension :
def my_def(list_comp):
global my_list
my_list = [[1,2],[3,4]]
list_comp_output = list_comp
return list_comp_output
my_def([])
print my_def([x[1] for x in my_list])
[2,4]
Also i don't see any thing logical here :) if you want to use a global variable just define if in the scope of your module (out side the function and pass it to your function.)
def my_def(list_comp):
# do stuff with list_comp
return list_comp_output
my_list= # a costume list
li=[x[1] for x in my_list]
print my_def(li)
Or more elegant do the list comprehension within your function :
def my_def(list_comp):
return [x[1] for x in list_comp]
1. Learning Python by Mark Lutz
That's because my_list is not defined;)
First you have to realize that the body in the function definition (however if there were default arguments, these would have been evaluated right away) is not executed until the function is actually call, but the argument to it is evaluated before the function is called.
The two first statements in it says first that the symbol my_list is that of the global scope in this function. The second says to assign to that symbol (in global scope) the value [[1,2],[3,4]]. But this does as mentioned not happen before the function is called (so right after the function definition it is still not defined).
The consequence of this is that you try to evaluate my_list (when calling the function) before it is defined or assigned to (in the function body).
You could try to make it happen by calling my_def([]) first, which will define my_list, but the correct way would probably be to put the definition of my_list outside the function definition:
my_list = [[1,2],[3,4]]
def my_def(list_comp):
list_comp_output = list_comp
return list_comp_output
print my_def([x[1] for x in my_list])
Which gives the answer:
[2, 4]

Variable scope (Python Newbie)

I am using a .pop method on a Global list inside a function block, but the Global list is being updated outside the block. I thought that local variables can't modify Global variables.
This should not work, but it does:
import random
PhraseBank = ['a','b','c','d']
def getPuzzle(secretphrase):
phraseIndex = random.randint(0,len(PhraseBank)-1)
secretphrase = PhraseBank.pop(phraseIndex)
return secretphrase #Returns and item indexed from the PhraseBank
while len(PhraseBank) != 0:
secretphrase = getPuzzle(PhraseBank) #works
print(secretphrase, PhraseBank)
OUTPUT is:
a ['b', 'c', 'd']
d ['b', 'c']
c ['b']
b []
Why is PhraseBank getting updated Globally when I am only modifying it inside a function block?
Lists are mutable. You are changing the list that PhraseBank refers to, but it's still referring to the same list. So the variable isn't changed (still refers to the same thing) but that thing has changed.
If you were assigning a value to the PhraseBank, it would not be changed unless you say you are using the global variable explicitly. However, each var in Python is just a named reference to an object. You read the var and then modify the object it refers to. Yes, you can't change the reference itself, but you can change the object.
So, what you faced is one of the most typical features of Python. Everything is an object, all the variables are references. Understanding that fact often helps to understand many things that may seem strange. A good example:
>>> li = [1]
>>> a = (li, li)
>>> a[0].append(1)
>>> a[1]
[1, 1]
>>> li
[1, 1]
>>> li.append(1)
>>> a
([1, 1, 1], [1, 1, 1])
If nothing surprises you in the behavior of the code above, then you understand how are the variables and objects related. :-) Here the variables that are not touched are changed, but not because they start referring other objects, but because the objects they refer to are modified. So do the tuples that are immutable. Yes, they are. A tuple, once created, always refers to the same objects. But each of those object may be changed.
You can't assign to a global variable inside a function (unless you explicitly use a global declaration). You can modify objects stored in global variables just fine.

Categories