Explain Objects in Python programming - Almost everything is an object in Python! In this piece we are going to talk about those objectsâŚ
First, what is an object? According to webopedia an object is âa self-contained entity that consists of both data and procedures to manipulate the dataâ. To draw a parallel with areal-world object, take a coffee machine: it has materials which represent the data (i.e. coffee beans), and it has functionalities which represent the procedures to manipulate the data (i.e. grinding the beans). Variables, class instances, functions, methods, and many other things are objects in Python.
Now letâs dive into the wonders of how the Python language treats everything as an object.
Every object in Python has an id, a type and a value.
The id of an object is its identityâââits address in memory. According to geeksforgeeks, âthis identity has to be unique and constant for this object during its lifetimeâ. In other words, when an object is created it gets an identity, or an address in memory, and when the object is deleted it loses this identity.** **For programmers who have never dealt with low-level languages, this concept may be new, but the fact is that everything in a computer is stored somewhere in the memory of the machine. The id of an object can be retrieved with function id().
Letâs take an example with an integer a of value 9.
>>> a = 9
>>> id(a)
10105344
In this case the id of the variable a
is 10105344
. Of course, this will depend on your machine and where the variable is saved by the Python interpreter.
The type of an object is, wellâŚits type. To check the type of an object, we can pass it to the<em> type()</em>
function. Letâs look at our a
variable:
>>> type(a)
<class 'int'>
Here we see that the function doesnât just return* *int
, as expected, but class âintâ
. In Python, there are no built in data types, as there are in C or other programming languages. The types are actually classes and each variable is an instance of the corresponding class. So in our case, a
is an instance of the int
class.
There are two main types of objects in Python: mutable and immutable
Mutable objects in Python are objects that can be modified. For example, lists, sets, and dictionaries are mutable. Letâs see what that means:
>>> l1 = [1, 2, 3]
>>> l2 = l1
>>> l1.append(4)
>>> print(l2)
[1, 2, 3, 4]
We created a first list of ints, l1
, and then assigned* l1
to a new list l2
. We then appended an element to l1
. By now l1
should look like [1, 2, 3, 4]
. But what about l2
? When we print it, we see that it has been updated too, even though we assigned l1
to *l2
before modifying l1
. What we did in the second line is called aliasing. We created an alias of l1
called l2
, but both those names point to the same object. We can verify this by using the id()
function we mentioned earlier, or the is
operator:
>>> id(l1)
140672713370824
>>> id(l2)
140672713370824
>>> l1 is l2
True
Both l1
and l2
have the same identityâââtheyâre pointing to the same object.
Note:* the**** *
<em>is</em>
operator is different from the<em>==</em>
operator. The former evaluates if two names point to the same object in memory and the latter evaluates if they have the same value.
If we want to make a copy of a list without modifying the original, we need to clone it. This can be done with the slicing method:
>>> l1 = [1, 2, 3]
>>> l2 = l1[:]
>>> l1.append(4)
>>> print(l2)
[1, 2, 3]
>>> id(l1)
140672713370824
>>> id(l2)
140672692500488
>>> l1 is l2
False
The cloning worked and l2
is now referencing to a different object than l1
, as their ids are different.
Numbers, strings and tuples are immutable objects in Python. That means that they canât be modified in place. Letâs take an example with ints:
>>> a = 89
>>> b = a
>>> a += 1
>>> a
90
>>> b
89
We see now that if a
equals b
and we modify a
, b
will remain unmodified. That is the meaning of immutable.
Weâve seen that you canât modify immutable objects after assignation, but you *can *do it with mutable objects.
So what goes on under the hood? With the example of our lists l1
and l2
, Python will reassign all the elements of the list to add a new element to it. This is not as efficient in terms of memory and time. Immutable objects, on the other hand, are quicker because the whole object is not reassigned every time we try to modify itâââPython will just create a new object with the updated value.
The way Python stores variables in memory is interesting. For people familiar with the C programming language, it works a bit like pointers. Variables in Python store the address (or the id) of the object they reference.
That being said, an argument is passed by** reference** to a function. That means we donât actually pass *objects *to functions, we pass *references *to them. In general, a function works this way: it has a name and either takes arguments or doesnât, executes a block of statements, and exits. So when we pass arguments to it shows different behaviors depending on the mutability of the object referenced by the argument.
If we pass a mutable object to a function, and modify its value inside the function block, the new value of that object is accessible from outside the scope of the function. To illustrate:
def foo(n):
n.append(5)
l1 = [1, 2, 3]
foo(l1)
print(l1)
Output:
[1, 2, 3, 5]
We print <em>l1 </em>
after passing it to our function foo(),
which appends a number to it, and then we see the updated list.
Now, if we try that with an immutable object, the modification brought to the argument inside the function will not be accessible from outside its scope
def foo(n):
n += " there"
print("The string inside the function is: {}".format(n))
s1 = "Hello"
foo(s1)
print("The string after the function is: {}".format(s1))
Output:
The string inside the function is: Hello there
The string after the function is: Hello
We can see that the string s1
has been modified inside the function, but still holds the old value outside the scope of foo()
.
We said that strings are immutable and that two variables with the same value point to the same object. In fact, thatâs not always the case. Letâs take a look at an example:
>>> a = "HBTN"
>>> b = "HBTN"
>>> a is b
True
>>> a = "H B T N"
>>> b = "H B T N"
>>> a is b
False
WaitâŚwhat? In both cases a
and* b
have the same valueâââwhy arenât they the same object in the second case? For one simple reason: when an instruction is run, there is already an allocated array of ints ranging from -5 (defined by the macro NSMALLNEGINTS
in the source code of Python) and 257 (NSMALLPOSINTS
). The most used ints in Python are in that range, so the interpreter already allocates them at the start. Creating objects in those ranges does not require an actual creation of object. Thatâs why all ints and characters (characters are just numbers) in that range are actually the same object and the condition a
is *b
evaluates to True. The ASCII value of the space character not being in that range, new objects are created for a
and b
and they donât point to the same object. Letâs see this with int values:
>>> a = 1024
>>> b = 1024
>>> a is b
False
Here a
and b
have the same value, but they arenât the same object because they are not inside the array of ints provided by Pythonâââfascinating!
We could think that using the operation a += b
and a = a + b
are the same, from a programming standpoint. But under the hood, they are very different operations. The += sign is called a compound assignment operator. It both assigns and operates on a variable, so it does what we call an in place addition. Since everything in Python is an object and stems from a class, these operators are actually methods from the class of the data they are used on. the += in turn calls the method __iadd__
, and if that doesnât work it will try to use __add__
instead. This can lead to unexpected behavior that is implementation dependent. The + operator only calls __add__
. In both cases, depending on the object they operate on, we can see both the creation of a new object or the modification of an existing object.
Remember how we said tuples are immutable? Well, itâs true, but if they contain mutable objects we can change the value of these objects. Letâs take an example:
# Modifying the first tuple will not affect the second
>>> a = (9, 2)
>>> b = a
>>> a += (0,)
>>> a
(9, 2, 0)
>>> b
(9, 2)
# Modifying the first tuple will modify the second
>>> a = (9, [1, 2, 3])
>>> a[1].append(4)
>>> a
(9, [1, 2, 3, 4])
>>> b = a
>>> a is b
True
>>> a[1].append(5)
>>> b
(9, [1, 2, 3, 4, 5])
We successfully modified the tuple! We did this by modifying one of its mutable values, which was also updated in its alias. We need to be careful thoughâââalways make sure that tuples put in sets and dictionaries only contain immutable types.
#python