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.

id and type

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.

Mutable objects

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.

Immutable objects

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.

How Python treats mutable and immutable objects differently

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.

How arguments are passed to functions and what that implies for mutable and immutable objects

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().

Other tricky object magic and exceptions

Preallocated range of ints

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 band 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!

Difference between a += b and a = a + b

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.

Tuple mutation

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

Explain Objects in Python programming
46.15 GEEK