Python: Everything is an Object, and Some Objects are Mutable

Larry Madeo
6 min readMay 25, 2017

--

Python deservedly has a reputation for being an easy language to read and write. It’s got great documentation and a community that is very welcoming to beginners. But, as you dig deeper, you may find aspects of the Python language that surprise you. One aspect that deserves in-depth explanation is variable assignment. I’ve seen two useful analogies that describe the nature of variables in Python. One is the “boxes vs. label” analogy. Another is the “names and bindings” analogy.

I’ll follow the names/bindings/objects model. In Python everything is an object: every integer, string, list, and function. What we might think of as a variable, instead think of it as a name. When we assign a value to a variable:

<name> = <object>

We are actually binding a name to an object. One implication of this is that multiple names can be bound to a single object. Here is a concrete example (feel free to try this at the Python command line):

>>> a = "spam"
>>> b = "spam"
>>> print(id(a))
140090288720896
>>> print(id(b))
140090288720896
>>> print(a is b)
True

First, a quick explanation: What does the id() function do? id() returns the actual memory location where the variable is stored. Since id(a) = id(b), we know that a and b both point to a single variable, that resides in a single memory location. This is what we mean by “multiple names bound to single object”.

Now let’s try a similar script using a list instead of a string:

>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> print(id(a))
140090289536200
>>> print(id(b))
140090288737736
>>> print(a is b)
False

In this case, you can see that the objects that a and b point to occupy different places in memory. Why did Python behave differently in this example? The difference is that a string is immutable, but a list is mutable. The above lines of code created two separate lists. To have the two names point to the same object, you could write the following:

>>> a = [1, 2, 3]
>>> b = a
>>> print(b is a)
True

An immutable variable cannot be changed after it is created (with one caveat). If you wish to change an immutable variable, such as a string, you must create a new instance and bind the variable to the new instance. A mutable variable can be changed in place. Refer to the graph at the beginning of this article for a list of the Python data types, and whether they are mutable.

Caveat: A tuple, which is immutable, can contain mutable elements such as a list ex: (1, 2, [3, 4, 5]) . The reference to the list within the tuple can remain unchanged, even if the contents of the list are modified. End of caveat.

Why Are You Telling Me All of This?!?

Simply put, if you don’t have a basic understanding of how Python works under the hood, you will discover that your scripts don’t behave as you expect, and you won’t know why. You, my friend, are now aware that when you say a = b ( a and b are both lists), you haven’t made separate copies of list, you have two names that both reference the same list object. So now, you won’t be surprised when modifications to a transfer to b and visa versa. With a good mental model of Python objects you will be better able to debug your programs.

>>> a = (1, 2, 3)
>>> print(type(a))
tuple
>>> b = [4, "five", 6]
>>> print(type(b))
list

FYI, in the CPython implementation of python, the id() function returns the address of the object that the variable points to. So, if the two print statements above print the same address, then the variable has been modified in place. However, I’m going to suggest that since a is a variable of type int, and since we know that int is an immutable variable type, we can anticipate that the variable a will not be modified in place. Instead, the number 3, which is stored someplace in memory will be left behind, and after a = 4 is executed, a will point to a new location in memory that contains the number 4. Go ahead and try this from the python command line!

Here is an analogy that has helped me to understand the use of variables in python: It is better to think of variables not as boxes, but as labels that are put on boxes. So, instead of the variable a being a box that contains the number 3, it is better to think of a box (or object) that contains the number 3, and we can have multiple labels (or variables) that can be applied to that box.For this reason it is possible for multiple “labels” to be applied to the same “box.” For example …

>>> a = “super hero powers”
>>> b = "super hero powers"
>>> print(a is b)
True

Using the is operator, we can test if a is b . And a is b. This is ture because Python sets aside a chunk of memory for the string “super hero powers.” If you don’t want to believe me, that is fine, because you don’t have to believe me, use the powers of Python! In Python there is the id function which returns a unique string which is the “identity” of an object.

Some people use the terms “name” and “binding” to more accurately reflect how variable names work in Python. When we say a = "super hero powers" , we are creating an object on the right side of the equal sign, and we are binding the name a to that object. When there are names on either side of the equal sign (ex: a = b), we are saying bind the name a to the same object that the name b is bound to.

Implications of passing mutable vs. immutable variables to functions …

def foo(a):
a = "new value"
abba = "old value"
foo(abba)
print(abba)

Consider the Python code above. It passes a string (which is an immutable type of object) into the function foo . Within the scope of the function foo, a has been bound to the same object that abba has been bound to outside the scope of the function. Within the scope of the function foo, we modify "old value"` to "new value" . But, as you’ll remember, strings are imutable, so a ends up pointing to a completely different object. Once we leave the scope of function foo , a is no longer in the name space, and the value that abba refers to was never changed.

Now let’s consider what happens when we pass a mutable variable to a function:

foo(a):
a[2] = "nothing"
bar = ['You', "know', 'something', 'Jon', 'Snow']
foo(bar)
print(bar)

The story is different when we consider passing a mutable variable into a function. When we modify a mutable object that has been passed into a function within the scope of that function, the changes happen in place, and the variable foo that still points to the same object will continue to point to it after code execution exits the function. To reiterate, passing in a variable causes the local name and the global name to point to the same object, and if we modify the mutable object in place (instead of pointing the name a to a completely new object), the changes will live beyond the scope of the function foo… So the above code snippet gives the following output: ['You, 'know', nothing', 'Jon', 'Snow'] And, as the Khaleesi attendants to the queen say, “It is known.”

If you like what you’ve read, follow me on Twitter and LinkedIn

Further Reading:

--

--