Everything is an Object (in Python).
Moving forward into learning software engineering, will inevitable put you against the concept of Object Oriented Programming, or OOP for short.
“Object-oriented programming (OOP) is a programming paradigm based on the concept of “objects”, which can contain data and code: data in the form of fields (often known as attributes or properties), and code, in the form of procedures (often known as methods).”
Referring specifically about Python, it is an extremely versatile programming language and that versatility comes from its object-oriented nature. As the title says, everything in Python is considered an object. Everything.
But what does this actually mean for real life purposes? That is what I will be trying to cover next. How objects are actually used to represent values in Python and what actually objects are in Python?
Let’s try to find out.
An object in Python is, well, an object, a thing. While this doesn’t seem too accurate it is actually true because remember: everything is an object in Python! In a little bit more technical explanation, we could say that an object represents a value that can be referred to by a variable. In the example below, the variable “a” refers to an object representing the value “1”.
>>> a = 1
In Python, all objects have a specific type which can be checked by using the built-in method called, well, type. Going back to the previous example, the type of “a” as it refers to the value “1” is the numeric type “int”:
```>>> a = 1>>> type(a)<class ‘int’>
By changing the value of “a” to another type of data, such as a string, the return of the type entry will obviously change to reflect the new type:
>>> a = “pepe”>>> type(a)<class ‘str’>
In this case the return is the sequence type “str”.
Each object exists within the computer memory somewhere. Therefore, each object exists within a physical memory address. In order to see the memory address of an object, the built-in method “id()” can be used.
>>> a = 1>>> id(a)2149256685872
The memory address is shown as a unique integer value which is considered as the identity of the object as long as the object exists.
While any physical memory address is, of course, unique, it is worth mentioning that, in Python, it is possible for two different variables to share the same identity. Comparing memory locations of two different objects can be done by using the “is” operator.
“is” will return “True” if the two compared variables have the same identity and “False” otherwise.
>>> a = 12>>> b = 21>>> a is bFalse
In this example “a” refers to the physical address of an “int” object (12), while “b” refers to a different memory location of another “int” object (21), therefore, both variables have different identities and the return result is “False”. The same statement could be seen the other way around:
>>> a = 12>>> b = 21>>> a is bFalse>>> a is not bTrue
While it could be assumed that the operator “==” does exactly the same as “is”, they are not the same as while “is” compares the physical address of two variables, “==” compares their values.
This is also explainable by referencing the difference between assignment and referencing. Assigning a value to a variable is not the same than referencing a variable to another variable which is at the same time assigned to a value.
Whenever an instance of an object is created, Python checks if such instance already exists in memory. If it does, the previously existing instance will be re-used. This is much easier to appreciate by using an example:
>>> a = 10>>> b = 10>>> a is bTrue>>> id(a)1469277170256>>> id(b)1469277170256
“a” is being assigned to an instance of the “int” object 10. Instead of creating a new instance when assigning “b”, Python checks if a previous instance already exists and then “b” is assigned to it. That is why the “id” operator returns exactly the same identity for both “a” and “b”. This is pretty much the same than assigning “a” to “b”.
>>> a = 13>>> b = a>>> a is bTrue>>> id(a)1573613824688>>> id(b)1573613824688
This process of object instantiation in which an instance is “recycled” is only applicable when we are talking about immutable objects.
An immutable object as the name suggests, cannot be changed without changing the identity of the object itself.
All numeric object types (such as int or float) are immutable types in Python. The same goes for strings.
As explained before, immutable objects can be used by more than one variable to refer to them. Take this string example:
>>> a = "Pepe">>> b = "Pepe">>> a is bTrue>>>
Both “a” anb “b” refer to the same “str” object “Pepe”. This is called aliasing. Whenever two or more variables refer to the same object it can be said that the object is aliased.
The value of immutable type objects cannot change without changing the object identity. Because of that, whenever we change the value that a variable refers to, we are actually changing the object of reference of that variable for a new one.
```>>> a = 15>>> id(a)1573613824752>>> a = 15 + 1>>> id(a)1573613824784
Python keeps an internal counter on how many references an object has. Once the counter goes to zero — meaning that no reference is made to the object — the garbage collector in Python removes the object , thus freeing up the memory.
Mutability, also as the name suggests, implies that mutable objects can change over time while keeping the same identity than originally.
An example of a mutable type in Python are lists:
>>> list_1 = [1, 2, 3]>>> list_2 = [1, 2, 3]>>> list_1 is list_2False>>> list_1 == list_2True>>> id(list_1)1573654126208>>> id(list_2)1573654942080
On this example we can see in a pretty clear fashion that while list_1 and list_2 have exactly the same value, they are in fact not the same instance (remember that the “==” operator compares values while “is” compares identities). You can see that both lists have different identity values.
Unlike immutable types, when creating an instance for a mutable object type, Python will not do a memory search looking for similar values but will simply create the new instance with a fresh identity.
A mutable object can afterwards, be altered, as many times as you wish, which will not change the object’s identity, as this is not tied to its value.
By using the “append()” method we can modify an existing list. See below:
>>> list = [1, 2, 3]>>> id(list)1573654931968>>> list.append(4)>>> print(list)[1, 2, 3, 4]>>> id(list)1573654931968
You can see that, even though the list was altered in order to append “4” to it, the identity value remains the same than before the change. This is the main point here. Changing the value of a mutable object will NOT change its identity.
A word about Tuples.
The immutable/mutable distinction in which changing the object’s value will change it identity or not is consistent for all object types but one: tuples.
Tuples are similar to lists, but unlike those, they are immutable objects and you cannot modify the values within a tuple… unless one of the values contained within a tuple is itself a list, which is in fact a mutable object. Something like this:
>>> tuple = (1, [2, 3])
Again, we could use the “append()” method to modify the “[2, 3]” list within the tuple.
>>> tuple = (1, [2, 3])>>> id(tuple)1573654891712>>> tuple.append(4)>>> print(tuple)(1, [2, 3, 4])>>> id(tuple)1573654891712
See that while the list did was modified, the identity of the tuple was not, as it should because remember, it is an immutable object.
Tuples are instantiated as mutable objects. This is done to consider the possibility that values contained in a tuple could change.
Why should I care?
Exactly. What difference does it make the mutable/immutable nature of an object? Quite a lot actually.
Take this function, for instance. It increments a number by 1:
>>> def increment(n):>>> ... n += 1
If we pass an integer to the function, that “int” value would keep its identity value regardless of being used by the function or not, because “int” objects are immutable — the reference to 1 passed to n in “increment” becomes a new object within the scope of the function, while the variable x retains the identity of 1 beyond it:
>>> x = 1>>> n = x>>> n += 1>>> x1>>> n2
This means that changes made to immutable objects within functions do not register beyond the scope of that function while changes made to mutable objects within functions DO register beyond the scope of the function.
Whenever you are writing a Python program, it is important to keep in mind the mutability properties of the objects that the variables passed to your functions will refer to.
As repeated several times along this article, whenever there’s an immutable objects instantiation, Python searches for previously existing objects with the same values before creating new instances. This explain this already seen example:
>>> x = 256>>> y = 256>>> x is yTrue
So, what about this?:
>>> x = 257>>> y = 257>>> x is yFalse>>>
What on Earth is going on here???
This behavior has to do with a specific characteristic of the CPython virtual machine. CPython is the reference implementation of Python. Written in C and Python, CPython is the default and most widely used implementation of the language.
Python is actually checking for pre-existing objects, but it is just searching within a PRE-DEFINED range of objects.
The idea is that certain values are more repeatedly used in programming. By being able to keep in cache these values, CPython reduces significantly the amount of time and memory consumed.
For integers, the range expands from -5 to 256, included, which explains the system reaction when 257 was used.
The pre-definition of these ranges occurs through an assignment of these values to the macros NSMALLPOSINTS and NSMALLNEGINTS.
- Everything is an object in Python.
- Immutable objects cannot change their values without changing their identity.
- When instantiating an immutable object, Python first checks if an object of the same value already exists in memory before instantiating a new instance.
- Mutable objects can change their values without modifying their identity.
- Mutable objects are created with no previous memory check for similar values and have a unique identity.
- Tuples are the only object type that, while immutable, can potentially modify their values.
- Changes made to immutable objects within functions do not register beyond the scope of the function.
- Changes made to mutable objects within functions do register beyond the scope of the function.