Names and values
Python is a very approachable programming language and it often works as you would expect. Be that as it may, you can encounter surprising behaviour. This is the case for variables. In this lesson we will discuss its underlying mechanisms inspired by Ned Batchelder’s presentation at PyCon 2015, which we strongly encourage you to watch.
If you come from C-based programming languages, variables are best thought of as containers. For example, when you write
string a = "hello";
you are defining a memory bucket named
a and putting the string
'hello' into it.
In Python, however, it’s a different story. A variable is best thought of a name that points to some value or object. Hence, there is no need to declare the type of a variable or require it to always point to information of the same type. In this sense, Python is said to be dynamically-typed so we can do something like this:
a = 'python' # a is a string a = 6 # now a is an integer a = [6, 7, 8] # now a is a list a = 'six' # now a is a string again
Moreover, a value can have many names. Let’s take a look at the following example:
A = [1, 2] B = A Z = [1, 2]
An important fact is that assignment never copies data. On one hand,
B refer to the exact same list. Neither
B is the real name, both have the same status. On the other hand,
Z refers to another independent list. The diagram below might be helpful to illustrate it.
Python offers the
id() function which returns the identity of an object and it is guaranteed to be unique among all objects. Along these lines, the
is operator returns
True if the identity of two names is the same.
print(A is B) # True print(A is Z) # False
In a different manner, the
== operator checks for equality.
print(A == B) # True print(A == Z) # True
At this point we have one list, referred to by two names and the following piece of code can lead to a big surprise.
A = [1, 2] B = A A.append('a') print(B) # B: [1, 2, 'a']
What has just happened? If we mutate an object through one of its names, the change will be visible on all other names.
This characteristic is only intrisic to mutable objects such as lists or dictionaries. Per contra, immutable objects such as numbers, strings or tuples cannot change in-place once they are created. All you can do is make new objects from old objects so we don’t have to worry about it.
A = "Hello" B = A A += " World" print(B) # B: Hello
In this way, if we reassign one name to a different object, it is done independently so all other names are not affected.
A = [1, 2] B = A A = [1, 2, 'a'] print(B) # B: [1, 2]
Another key aspect is that lists contain references to its elements and not copies. For instance:
A = [1, 2] B = [A, A] print(B) # B: [[1, 2], [1, 2]] B = 7 print(A) # A: [7, 2] print(B) # B: [[7, 2], [7, 2]]
We observe the same behaviour in this case where
append() modifies the list in-place.
A = [1, 2] B = [A, A] print(B) # B: [[1, 2], [1, 2]] B.append('a') print(A) # A: [1, 2, 'a'] print(B) # B: [[1, 2, 'a'], [1, 2, 'a']]
In order to get a sense of what is happening in this program find below a visualization made with Python Tutor. This online tool allows you to enter your own code and it will animate its behaviour line by line.
What if we just wanted to modify
B and avoid the domino effect on both
A? We could assign two different copies of
A by means of
A = [1, 2] B = [list(A), list(A)] print(B) # B: [[1, 2], [1, 2]] B.append('a') print(A) # A: [1, 2] print(B) # B: [[1, 2, 'a'], [1, 2]]
Let’s visualize it so as to wrap our head around it.
What if we replaced
+? In this case, the
+ operator doesn’t change the list in-place but it returns a new list. Therefore, we obtain the same result as before but note that the assignment process is different.
A = [1, 2] B = [A, A] print(B) # B: [[1, 2], [1, 2]] B = B + ['a'] print(A) # A: [1, 2] print(B) # B: [[1, 2, 'a'], [1, 2]]
Again, a visualization comes in handy.
Universitat Politècnica de Catalunya, 2020
Prohibit copiar. Tots els drets reservats.
No copy allowed. All rights reserved.