For Python, Everything is an Object
In Python, everything is an object, literally … from variable types to functions. And this is why it is important to understand how Python works with each of these objects.

As Guido van Rossum says in The Python Language Reference (2012), objects in Python are abstractions of data. All data in a Python program is represented by objects or by relationships between objects. Booleans, integers, floats, strings, data structures, program functions… all of them are implemented as an object. Although it can be a bit abstract, it may be easier to visualize an object as a box that contains data inside.
Each of these objects/boxes has an identity, a type, and a value. Once the object is created the object identity never changes. You might think of this identity as the object’s memory address (something called pointers in C). To know the identity of an object, we only have to use the id()
function, which returns an integer representing its address in memory. In this way, we can determine, for example, whether or not two variables point to the same object. It is not the same that two variables have the same value, as two variables refer to the same thing. Let’s see an example:

Likewise, it is possible to know the type of a variable/object with the type()
function. For example, a variable number that has the value (5) will be of type integer. It is essential to know that the type of object is unchangeable. But why is it important to recognize the object’s type? Because the type of object determines the operations that can be realized with that object and the possible values that objects of that type can have.

Additionally, knowing the type of object that we are handling, allows us to determine if an object is mutable or immutable. This allows us to know if we can or cannot modify the value of that object.
Mutable and immutable objects
In Python, the value (or state) of some objects can be modified. Based on how feasible those modifications are, objects can be mutable or immutable. When we talk about the state of an object, we refer to the information we have about it: the length of a string object or the value of an integer.
Objects whose value or state can be changed are called mutable. Objects with values or states that will never change after being created are called immutable. And this is the importance of knowing the type of object. The mutability of an object is determined by its type. For example, numbers (integers and floats), strings, and tuples are immutable. Dictionaries (beware, not dictionary keys!), lists, and byte arrays are mutable.

Returning to the analogy of objects as boxes: A mutable object is like an open box. With a box left open, we cannot only see what is inside the box, but we can change or modify it. But be careful! Python is a strongly typed language (that’s how it is known). So, even if what is inside the box is mutable, the box type cannot be modified.
On the other hand, an immutable object is like a closed box but with a window. We can see what’s inside the box, but nothing else. We cannot modify the value inside this box.

Here we must talk about something additional. Variables in Python are just names or labels that assign a kind of name to the object that contains the data. So an assignment doesn’t copy the value. It just places a kind of identification like a “post-it” note on the box. The name then is just a reference to the object, not the object itself.
It is for this reason that in Python we can observe things like these:
Python optimizes resources and, when it determines that two different names of an immutable object (a and b) have the same value, it makes them point to the same object. And with this, we can use something known as ‘aliasing’ in which if we assign one variable to another, they ultimately refer to the same object:

And you must be very careful because changes in one variable directly affect the other. After all, it is the same object that is being modified. Strings are immutable. They never change. If we need to modify a string, we need to do something additional. For example, reassign the variable with a new value or create a copy.
Something different happens with mutable objects like lists. We can have variables a and b with the same values, but that does not imply that we are referring to the same object.

But, why does it matter, and how differently does Python treat mutable and immutable objects?
Because this directly affects the efficiency of the code we are writing. For example, immutable objects are easier to access than mutable ones. But modifying them is much more expensive since Python needs to create copies of the object with each modification. And consequently, this involves a cost of memory.
In a concrete case, to modify the value of a variable ‘a’ that contains an integer (immutable) Python must allocate new memory space for a new ‘a’. After that, Python needs to modify the variable’s memory address, so now it points to another space memory. A different memory space from the initial one (even if the variable name is the same ‘a’).

On the other hand, mutable objects can be modified easily and it does not imply having unnecessary copies because the value is modified directly. For example, in the case of a list, we only have to modify the values, not copy them completely to modify.

Additionally, being an immutable object makes debugging easier. Because the value is unchanged, it is constant. The unchangeable value allows us to know the state of that object more quickly.
Knowing whether an object is mutable or immutable is also very important when we try to understand how arguments are passed to functions in Python.
How are passed arguments to functions and what does that imply for mutable and immutable objects?
Unlike languages like C, which allow arguments to be passed by value or reference to a function, Python uses a mechanism called “Call-by-Object”, “Call by Object Reference” or “Call by Sharing”.
And why do we talk about this when we were discussing mutable and immutable objects? Because they are related. It is relevant to be clear about this difference when we are working with functions.
When an immutable object (such as an integer, a string, a tuple …) is passed as an argument to a function, it is passed as a value, not as a reference. And it makes sense if we know that as immutable objects these cannot be modified. So no matter what happens in the function, the values are not going to be modified directly.
On the other hand, when the arguments are mutable objects, they are passed as a reference to the objects and can be modified by the function. So, if we pass to a function a list, the list’s values can be modified by the function itself. Otherwise, if a new list is assigned to the variable name, the original list is not affected.
And this can have an unwanted effect. We need to be careful and keep in mind the type of objects we are passing as arguments (and if they are mutable or immutable). Careless, we can modify an object’s value that we did not want to modify. If, for example, we do not want to modify the original list that we pass as an argument we must create a copy of the list within the function. That way, we will have a new memory address and a new list as a local variable.
As we have seen throughout the article, knowing the type, identity, and value of objects in Python takes a lot of relevance. Because as we said at the beginning: Everything in Python is an object, literal.
References:
Rossum, GV(2012). The Python Language Reference. Release 3.2.3. Python Software Foundation.
Johnson, MJ (). A concise introduction to programming in python. Chapman & Hall/CRC Textbooks in Computing.
Real Python - Immutable vs mutable objects: https://realpython.com/lessons/immutable-vs-mutable/
Python Course - Parameter Passing: https://www.python-course.eu/passing_arguments.php#:~:text=If%20you%20pass%20immutable%20arguments,all%2C%20i.e.%20they%20are%20immutable.
More content at plainenglish.io