Welcome to my blog post where we will dive into the world of Python variables. If you are new to programming, don't worry, I have got you covered! In my previous blog post, I provided a refresher on the basics of Python programming language.

Now, we will move on to the next level and take a closer look at variables in Python. Variables are one of the fundamental concepts in programming and mastering them is essential for writing efficient and effective code.

So, let's get started and explore Python variables in-depth!

Table of Contents


Memory

You can think of memory as a set of blocks where each block has a unique address. Think of it like a real-world example where each house on a street has a unique address. In the same way, each block has a unique address.

Now, let's dive into variables.

Variable

What happens when you write a = 5?

Here, I declared a variable a with a value of 10. Let's understand what happened under the hood.

Let's take a look at another example.

s = "hello"
print(s)
print(id(s))
print(hex(id(s)))

# ------------------------- OUTPUT --------------------- #
hello
4702080944
0x118440fb0

Reference Counting

getrefcount()

import sys

# delcared a list with respective values, print it's id and then get the reference count
lst_1 = [1,2,3]
print(id(lst_1))
sys.getrefcount(lst_1)

# --------------------- OUTPUT --------------------- #
4389242752
2

ctypes

# with `ctypes`, you can get the actual reference count as it takes the actual memory address and not the reference.
import ctypes

def ref_count(address):
    return ctypes.c_long.from_address(address).value

# here you can see you get 1 as the reference count which is correct
print(id(lst_1))
ref_count(id(lst_1))

# -------------------- OUTPUT ------------------ #
4389242752
1

Garbage Collection

Now, we disabled the garbage collector, so that we can run it manually.

# create an instance of class A
my_var = A()


# -------------- OUTPUT --------------- #
B: self: 0x11953e8c0, a: 0x11953d8d0
A: self: 0x11953d8d0, b:0x11953e8c0

Now, let's point my_var to None, so we'll only have a circular reference.

my_var= None


print('refcount(a) = {0}'.format(ref_count(a_id)))
print('refcount(b) = {0}'.format(ref_count(b_id)))
print('a: {0}'.format(object_by_id(a_id)))
print('b: {0}'.format(object_by_id(b_id)))

# ------------------ OUTPUT -------------------- #
refcount(a) = 1
refcount(b) = 1
a: Object exists
b: Object exists

We enabled the garbage collector and then the garbage collector removed both the objects and you can see that both the objects are not found.

Object Mutability

Now, let's see some examples of mutable and immutable datatypes and understand what happens under the hood of mutable and immutable datatypes when you change their values.

Immutable

s = 'python'
print(s)
print(hex(id(s)))

# ----------------- OUTPUT ------------------ #
python
0x10380b870


s = 'hello'
print(s)
print(hex(id(s)))

# -------------- OUTPUT ------------------- #
hello
0x106232470

As you can see both the addresses are different.

Mutable

# creating a list and printing out the list and it's address
my_list = [1, 2, 3]
print(my_list)
print(hex(id(my_list)))

# ------------------- OUTPUT ------------------ #
[1, 2, 3]
0x11bf06340


# checking if the address is changed after modifying the list

my_list.append(4)

print(my_list)

print(hex(id(my_list)))

# ------------------ OUTPUT -------------------- #
[1, 2, 3, 4]
0x11bf06340

Mutable Datatype Within Immutable Datatype

Function Arguments and Mutability

Immutable Objects as Arguments

Mutable Objects as Arguments

Mutable Objects inside Mutable Objects as Arguments

Shared References and Mutability

The same thing will happen with mutable objects.

# doing the same thing with mutable objects
my_list_1 = [1, 2, 3]
my_list_2 = my_list_1
print(my_list_1)
print(my_list_2)


print(hex(id(my_list_1)))
print(hex(id(my_list_2)))

Python Interning

Integer Interning

In this example, both a and b are assigned the integer value 10. Since this value is within the range of interned integers, Python interns it and assigns the same object to both a and b. Therefore, a is b returns True.

String Interning

It is important to note that interning is an implementation detail of the Python language and may vary depending on the Python interpreter being used. Therefore, it is recommended to rely on the == operator to compare values of integers and strings instead of using the is operator.

Variable Equality

Everything is an Object

For example, if you declare a variable in Python, such as:

x = 42

The value 42 is an object of the int class, which means it has built-in methods and attributes that can be accessed and manipulated.

Similarly, if you define a function in Python, such as:

def my_function():
    print("Hello, World!")

The function my_function() is an object of the function class, which means it can be passed around as a variable, returned from another function, or even assigned to a different name.

The concept of everything being an object in Python is a fundamental aspect of the language and is important for understanding how Python code is executed and how objects interact with one another. It also allows for powerful programming constructs such as dynamic typing, duck typing, and metaprogramming, which can make Python code more flexible and expressive.

Conclusion

In conclusion, understanding variables and their behavior in Python is crucial for writing effective and efficient code. Variables are placeholders for data that are stored in memory, and their mutability determines whether they can be changed or not. Memory management is an important consideration when working with variables, as it can impact the performance of your code. Shared references can lead to unexpected results, so it is important to be aware of how they work. Finally, understanding function argument mutability can help you avoid errors when passing variables between functions. By keeping these concepts in mind, you can write better Python code and avoid common pitfalls.


Also published here.