Variables are not that easy to understand for students. Often the name of the variable is something that has an ontological meaning for them, other times the usage of a variable in both the left and the right side of an assignment may be confusing.
But something they should not underestimate is the mixed management of references in Python.
To start, let’s first discuss scope (or visibility). Is a variable usable in a specific piece of code? Can I use the same name for a different variable?
While in other programming languages with a more strict syntax, the scope is straightforward, in Python is a bit more complicated.
Let’s start considering the simplest case: a global variable and its usage inside a function
a=1000 def f(): print "Inside f",a print a f() print a
So, the variable inside the function has the same value. Just to make thing more clear, you should remember that the value of a is the one at the time of execution of f(). I.e. it doesn’t matter if the variable is initialized before the definition:
def f(): print "Inside f:",a a = 73 f() print a
What if I change the value of variable a inside f?
def f(): a = 256 print "Inside f:",a a = 73 print a f() print a
As you can see, the variable inside the function is a different one. This is the scope in action: visibility of the inner variable is limited to the function. That applies also for parameters:
def f(a): a = 256 print "Inside f:",a a = 73 print a f(a) print a
So, everything assigned after the function name is local. Unless it is a reference.
A reference is like a pointer to something else: the memory location storing the information is the same, but it has different labels (i.e. variable names). This is true for lists:
l = [1,4] t = l l=7 print l print t
in this case, t is not a copy of l, it is just a different name for the same object. So changing the content of l would change the content of t, and vice-versa.
This happens for function parameters too: lists and dictionaries are passed as references, so changing the value inside a function would change the value of the original variable.
def f(a): a=99 print "Inside f:",a a = [12,73] print a f(a) print a
This is a classic example of a function with a side effect: the function changes something and you should be aware of that. Another example for side-effects due to references can be the following.
Consider that you may want to record every element added to any list into another one, like a log. What happens when you use the same function on the log itself?
log =  def add(el,l): l.append(el) log.append(el) a = [12,73] add(2,a) print a print log add(99,log) print log
This happens because l and log point to the same memory location.
This is even worse when you try to create matrices using lists of list. Try the following:
def create_matrix(rows,cols): matrix = [*cols]*rows return matrix m = create_matrix(3,4) for row in m: print row m=7 for row in m: print row
As you can see, changing an element in a row will change all the other elements in the same position in the other rows. This is because we have just one list as row, and every other is a reference to the same. While *cols duplicates the integer value 0 (not a reference, but an actual value), the duplication of the rows is exactly as the assignment of a list to a different variable name: it creates a reference to the previous one.
Then, if you want to create a matrix, it may be better to use:
def create_matrix(rows,cols): matrix =  for i in range(rows): r =  for j in range(cols): r.append(0) matrix.append(r) return matrix m = create_matrix(3,4) for row in m: print row m=7 for row in m: print row