Python is an interpreted, high level and general purpose programming language created by Guido van Rossum. Released in 1991, its name, besides the idea of a real python that comes in mind, is based in the famous british comedy troupe, Monty python.
Python allows us to program in procedural, object-oriented, and functional style, but today, I will cover the fundamental concepts and techniques for doing object-oriented programming in Python. This language is used in many tech fields and by huge firms like Google, Tesla, Facebook, Twitter and even by the NASA.
Python is an object oriented programming language, this means that in order to solve problems it uses objects. In this model, an object is an abstraction of reality, a collection of data that could represent anything, from a dog or a table to a bank transaction. Python is based on classes. A class is like a template or structure upon objects are created.
Let’s dive a little bit more into objects then.
In Python we can say everything is an object, for instance, a data type, like an integer or string, internally is treated like an object, not like a data type. Therefore objects have methods and attributes . A method is a function owned by the object and an attribute is a property of the object (if we think of a dog attibutes could be race, age, color, and so on). Let’s explain a method with an example:
In the las image, we can see that a string is really an object and it has methods like “split” (to divide a string with a separator, by default the space).
Eventhough everything is an object, there are different types of them, so to know wich type is one it is possible to use the builtin function type() (see example below).
An object is located somewhere in the memory of a computer, to know where, we can use the builtin function id(), which returns a number showing the address location of that object in memory. An object’s identity or id is an integer, guaranteed to be unique and constant for this object during its lifetime. We can use this to make sure two objects are referring to the same object in memory or not.
Objects in python could be mutable or inmutable, but what does this mean?
When an object is initiated, as I told, it is assigned a unique object id. Its type is defined at runtime and once it is set, it can never change, however its state can be changed if it is mutable. A mutable object can be changed after it is created, and an immutable object can’t. Examples of mutable objects are lists, dict, set, byte array. Changing the value of a mutable object will NOT change its identity as you can see in the next image.
On the other hand, Bytes, complex, floats, frozen sets, integers and strings, are immutable types; these can not be changed. In the following image it is possible to see how an inmmutable object can not change its value and a new object is assigned to the variable.
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 process of object instantiation in which an instance is “recycled” is only applicable when talking about immutable objects.
There are vast differences between mutable and immutable types and how they are treated when passed onto functions. Memory efficiency is highly affected when the proper objects are used.
For example, if a mutable object is called by reference in a function, it can change the original variable. Hence, to avoid this, the original variable needs to be copied to another variable. Immutable objects can be called by reference because their value cannot be changed anyway.
The same object is passed to the function in the above example, but the variable’s value doesn’t change even though the object is identical. This is called pass by value. So what is exactly happening here? When the function calls the value, only the variable’s value is passed, not the object itself. So the variable referencing the object is not changed, but the object itself is being changed but within the function scope only. Therefor the change is not reflected.
Finally, I give two more examples to see the differences between mutable and immutable objects:
- We can see in this example, the object’s reference has not been tranfered but a copy of it. This happens with immutable objects.
- On the other hand, in the next image, we can see how the reference has been transfered, therefor, an element has been added to the object.
In C, when we assign a variable, we first declare it, thereby reserving a space in memory and storing the value in the memory spot allocated. We can create another variable with the same value by repeating the process, ending up with two memory spots, each with its own value equivalent to the others.
Python employs a different approach. Instead of storing values in the variable’s memory space, Python has the variable refer to the value. Similar to pointers in C, Python variables refer to values (or objects) stored somewhere in memory. In fact, all variable names in Python are references to the values, some of which are front-loaded by Python and therefore exist before the name references occur (more on this later). 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.
Each time we create a variable that refers to an object, a new object is created.
>>> list_1 = [1, 2, 3]
>>> list_2 = [1, 2, 3]
>>> list_1 == list_2True # list_1 and list_2 have the same value
>>> L1 is L2False # list_1 and list_2 do not refer to the same object!
However, we can have two variables refer to the same object through a process called “aliasing”: assigning one variable the value of the other variable. In other words, one variable now serves as an alias for the other since both of them now refer to the same object.
>>> list_1 = [1, 2, 3]
>>> list_2 = list_1 # list_2 now refers to the same object as list_2
>>> list_1 == list_2
>>> list_1 is list_2
[1, 2, 3, 4]
Since list_1 and list_2 refer to the same object, modifying L1 results in the same change in list_2.
Exceptions with Immutable Objects
While it is true that a new object is created each time we have a variable that refers to it, there are few notable exceptions:
- some strings
- Integers between -5 and 256 (inclusive)
- empty immutable containers (e.g., tuples)
These exceptions arise as a result of memory optimization in Python implementation. After all, if two variables refer to objects with the same value, why wasting memory creating a new object for the second variable? Why not simply have the second variable refer to the same object in memory?
These objects are always reused or interned. The rationale behind doing this is as follows:
- Since programmers use these objects frequently, interning existing objects saves memory.
- Since immutable objects like tuples and strings cannot be modified, there is no risk interning the same object. But tupples are something in between because eventhough they are immutable can contain mutable objects like lists, so at the end they can be modified.
Remember the integer expression list from -5 to 256? In Python, it results in two macros: NSMALLPOSINTS and NSMALLNEGINTS that will store the most used integers by programmers.
It is actually an array of 262 integers (most commonly used). And this structure is basically used to access these integers fast. They get allocated right when you initialize your NSMALLPOSINTS and NSMALLNEGINTS. So, when we create an int in that range, we’re actually just getting a reference to the existing object in memory.
#define NSMALLPOSINTS 257
#define NSMALLNEGINTS 5
Along this blog I tried to make a point: in python every element is and it is treated like an object. Did I convince you? Let me know…