Sitemap
Morgane.a

Morgane.a

Python: Mutable, Immutable… Everything Is an Object!

A beginner-friendly explanation of Python objects, identity, mutability, references, and how function arguments really work.

8 min readMar 31, 2026

--

Press enter or click to view image in full size
Python: everything is object

When I started learning Python, one thing kept confusing me: variables did not always behave the way I expected. Sometimes changing one variable had no effect on another, and sometimes it unexpectedly did. At first, it felt inconsistent. But while working on this project, I realized that Python is actually being very consistent — I just did not yet understand the rule behind it.

That rule is simple, but incredibly important: everything in Python is an object. Once that idea really clicked, a lot of behaviors that seemed strange at first started to make sense.

In this article, I want to explain that idea in a clear and practical way, using the concepts covered in this project:

  • id()
  • type()
  • mutable objects
  • immutable objects
  • references
  • aliasing, assignment, equality, and identity
  • how arguments are passed to functions

id() and type()

A good place to start is with two very useful built-in functions: type()and id(). The type()function tells us what kind of object we are working with, and the id()function tells us the identity of that object.

In Python, that identity corresponds to the memory address of the object. In other words, every object in CPython has a type, a value, and an identity.

a = 42
print(type(a))
print(id(a))

Output:

<class 'int'>
140732180123456

The exact number returned by id() will depend on the machine and the Python session, so that part is not important by itself. What matters is that it shows us that the object exists somewhere in memory and has its own identity.

The same applies to other kinds of objects:

l = [1, 2, 3]
print(type(l))
print(id(l))

Output:

<class 'list'>
140732180567888

This is also where it becomes important to understand the difference between equality and identity. Two objects can have the same content without being the same object.

a = [1, 2, 3]
b = [1, 2, 3]

print(a == b)
print(a is b)
print(id(a))
print(id(b))

Output:

True
False
140732180567888
140732180567920

Here, a == b is True because the two lists contain the same values. But a is b is False because they are not the same object in memory. That distinction is essential. It also introduces another core idea from this project: a variable in Python is not really a “box” that stores a raw value. It is more accurate to think of it as a name bound to an object.

Mutable objects

A mutable object is an object that can be changed after it is created. Some common built-in mutable types in Python are list, dict, set, and bytearray. Lists are often the clearest example.

l1 = [1, 2, 3]
l2 = l1

print(id(l1))
print(id(l2))

l1.append(4)
print(l1)
print(l2)

Output:

140732180567888
140732180567888
[1, 2, 3, 4]
[1, 2, 3, 4]

This example is important because l2 = l1 does not create a copy of the list. It simply creates another name for the same object. That is called an alias. Since the list is mutable, changing it through l1 also changes what we see through l2, because both names point to the same object.

That is one of the most important lessons in this topic: assignment does not automatically mean copy.

The same thing happens with dictionaries:

d1 = {"name": "Alice"}
d2 = d1
d1["age"] = 25

print(d1)
print(d2)

Output:

{'name': 'Alice', 'age': 25}
{'name': 'Alice', 'age': 25}

Again, both variables refer to the same mutable object.

Another example that helped me understand this better is the difference between modifying a list in place and creating a new list:

l1 = [1, 2, 3]
l2 = l1
l1.append(4)

print(l2)

Output:

[1, 2, 3, 4]

But now compare it with this:

l1 = [1, 2, 3]
l2 = l1
l1 = l1 + [4]

print(l1)
print(l2)

Output:

[1, 2, 3, 4]
[1, 2, 3]

These two pieces of code look very similar, but they do not do the same thing. append() modifies the existing list object. On the other hand, l1 = l1 + [4] creates a new list and then reassigns l1 to that new object. The original object is still there, and l2 still points to it.

Immutable objects

Immutable objects work differently. An immutable object cannot be changed after it is created. Common built-in immutable types in Python include int, float, bool, str, tuple, bytes, frozenset, and range.

Integers are a classic example:

a = 89
b = a

print(a is b)
print(id(a))
print(id(b))

a = a + 1

print(a)
print(b)
print(id(a))
print(id(b))

Output:

True
140732180123000
140732180123000
90
89
140732180123032
140732180123000

At the beginning, a and b refer to the same object 89. But when we write a = a + 1, Python does not modify the object 89. It cannot — integers are immutable. Instead, Python creates a new object 90 and makes a point to that new object. Meanwhile, b still points to 89.

Strings behave in the same general way:

s1 = "Best School"
s2 = s1

print(s1 == s2)
print(s1 is s2)

s1 = s1 + "!"

print(s1)
print(s2)

Output:

True
True
Best School!
Best School

The original string was not changed. A new string was created and assigned to s1.

Tuples are also immutable, but they come with an interesting subtlety. A tuple cannot be modified directly, but it can contain mutable objects inside it.

t = ([1, 2], 3)
t[0].append(4)
print(t)

Output:

([1, 2, 4], 3)

The tuple itself did not change structure, but the list inside it changed, because that inner list is mutable.

Get Morgane.a’s stories in your inbox

Join Medium for free to get updates from this writer.

Another detail that often surprises beginners is that a tuple with one element needs a comma:

a = (1)
b = (1,)

print(type(a))
print(type(b))

Output:

<class 'int'>
<class 'tuple'>

So a = (1) is not a tuple. It is just the integer 1 inside parentheses. To create a one-element tuple, Python needs the comma.

Why does it matter, and how differently does Python treat mutable and immutable objects?

This difference matters because it explains a lot of Python behavior that seems strange at first. It is also the kind of thing that can easily lead to bugs if we do not understand what is happening.

For example:

a = 1
b = a
a = 2
print(b)

Output:

1

And now compare it with this:

l1 = [1, 2, 3]
l2 = l1
l1[0] = "x"
print(l2)

Output:

['x', 2, 3]

Why does b stay unchanged in the first example, while l2 changes in the second? Because integers are immutable, while lists are mutable. In the first case, a = 2 makes a point to a new object. In the second case, l1[0] = "x" changes the existing list object, and l2 still points to that same object.

This is also why identity and equality are two different things:

a = [1, 2, 3]
b = [1, 2, 3]
c = a

print(a == b)
print(a is b)
print(a is c)

Output:

True
False
True

a and b are equal in value, but they are different objects. a and c are not only equal — they are the exact same object.

This topic matters because it changes the way we read code. Instead of guessing, we can ask the right questions:

  • Is this object mutable or immutable?
  • Is this operation modifying the object, or creating a new one?
  • Are these two variables equal, or are they actually pointing to the same object?

Another important consequence is copying. If we want a real new list, we need to copy it:

l1 = [1, 2, 3]
l2 = l1[:]

print(l1 == l2)
print(l1 is l2)

Output:

True
False

The lists have the same content, but they are not the same object.

How arguments are passed to functions, and what does that imply for mutable and immutable objects?

One of the most useful things I learned from this project is how Python passes arguments to functions. Python passes arguments by object reference, which means the function receives a reference to the same object. But what happens inside the function depends on whether we mutate the object itself or simply rebind the local variable.

Here is an example with an immutable object:

def increment(n):
n += 1

a = 1
increment(a)
print(a)

Output:

1

Even though n was increased inside the function, a did not change. That is because n += 1 created a new integer object and rebound the local variable n to it. It did not modify the original integer object.

Now compare that with a mutable object:

def add_item(lst):
lst.append(4)

my_list = [1, 2, 3]
add_item(my_list)
print(my_list)

Output:

[1, 2, 3, 4]

Here, the function modifies the list object itself. Since lst and my_list refer to the same object, the change is visible outside the function.

Now look at this case:

def assign_value(n, v):
n = v

l1 = [1, 2, 3]
l2 = [4, 5, 6]
assign_value(l1, l2)
print(l1)

Output:

[1, 2, 3]

This is a very important distinction. Even though l1 is mutable, it was not modified. Inside the function, n = v only reassigns the local variable n. It does not change the original object that l1 refers to.

So the real difference is this:

  • if a function mutates a mutable object, the change is visible outside the function
  • if a function only rebinds its local variable, the original variable outside the function is unaffected

That explains why mutable and immutable objects can appear to behave differently in functions, even though the mechanism of passing arguments is the same.

Extra examples that really helped me understand the topic

Some smaller examples from the project made the whole topic even clearer. One of them is the behavior of +=, which can look similar across types but does not always do the same thing.

With a list:

a = [1, 2, 3]
print(id(a))
a += [4]
print(id(a))

Output:

140732180567888
140732180567888

The id() stays the same because the list is modified in place.

But with an integer:

a = 1
print(id(a))
a += 1
print(id(a))

Output:

140732180123000
140732180123032

This time, the id() changes because integers are immutable, so Python creates a new object instead of changing the old one.

That contrast captures the heart of this project really well. The syntax may look similar, but the behavior depends on the type of object and on whether that object can be changed in place.

Conclusion

This project genuinely changed the way I think about Python. Before, I mostly saw variables as places that stored values. Now I understand them more accurately as names bound to objects. That one shift in perspective makes a huge difference. It explains references, aliasing, assignment, equality, identity, copying, mutability, immutability, and even function behavior. More than that, it makes Python feel less mysterious. Instead of memorizing separate rules, I can now follow the logic of what the language is doing. For me, that is what made this topic so valuable: it did not just help me answer project questions — it gave me a much stronger mental model of Python.

Thank you so much for reading — I hope this article helped make Python objects, references, and mutability a little clearer.

--

--