DEV Community

Nasir Mustafayev
Nasir Mustafayev

Posted on

Everything is an Object in Python — And Why That Matters


When I started learning Python, I kept running into questions like:

  • Why does changing a list inside a function affect the original?
  • Why does a is b sometimes return True and sometimes False even when the values are the same?
  • Why does a += [4] behave differently from a = a + [4]?

These aren't random quirks — they all come down to one fundamental idea: in Python, everything is an object. Once you understand that, a lot of confusing behavior starts making sense.


id() and type() — Every Object Has an Identity and a Type

In Python, every object has two things:

  • type() — what kind of object it is
  • id() — where it lives in memory (its unique identity)
a = 89
print(type(a))   # <class 'int'>
print(id(a))     # 140234567891234  ← memory address
Enter fullscreen mode Exit fullscreen mode

Think of id() like a home address — two people can have the same name (value) but live at different addresses (identity).

a = [1, 2, 3]
b = [1, 2, 3]

print(a == b)    # True  ← same values
print(a is b)    # False ← different objects in memory
Enter fullscreen mode Exit fullscreen mode

This is the difference between == (same value?) and is (same object?).


Mutable Objects — Things That Can Change

A mutable object can be changed after it's created — without creating a new object. Lists, dictionaries, and sets are mutable.

a = [1, 2, 3]
print(id(a))     # 139926795932424

a.append(4)
print(id(a))     # 139926795932424 ← same address!
print(a)         # [1, 2, 3, 4]
Enter fullscreen mode Exit fullscreen mode

The list changed — but the object is still the same one in memory. This is what "mutable" means: the object itself can be modified in place.

The += vs = + Trick

This is where mutability gets interesting. Consider this:

a = [1, 2, 3]

a += [4]         # modifies in place → same address
print(id(a))     # same!

a = a + [4]      # creates a NEW list → new address
print(id(a))     # different!
Enter fullscreen mode Exit fullscreen mode

a += [4] calls a.__iadd__([4]) — the i stands for in-place. It extends the existing list without creating a new one.

a = a + [4] creates a brand new list, then assigns it to a. The old list is gone.

This matters a lot when multiple variables point to the same list:

a = [1, 2, 3]
b = a            # b points to the SAME list

a += [4]         # modifies in place
print(b)         # [1, 2, 3, 4] ← b sees the change!

a = a + [4]      # creates a new list
print(b)         # [1, 2, 3, 4] ← b doesn't see this change
Enter fullscreen mode Exit fullscreen mode

Immutable Objects — Things That Cannot Change

An immutable object cannot be changed after it's created. Integers, strings, floats, and tuples are immutable.

a = 89
print(id(a))     # some address

a = a + 1        # creates a NEW integer object
print(id(a))     # different address!
Enter fullscreen mode Exit fullscreen mode

When you "change" an integer, Python actually creates a brand new integer and points your variable to it. The original object is untouched.

A Confusing but Important Special Case

You might think (1) is a tuple — but it's not:

type((1))    # <class 'int'>   ← just grouping, like in math
type((1,))   # <class 'tuple'> ← the comma makes it a tuple
type(())     # <class 'tuple'> ← empty tuple, special case
Enter fullscreen mode Exit fullscreen mode

The comma is what makes a tuple, not the parentheses. And () is a special case — there's nothing else empty parentheses could mean, so Python treats it as an empty tuple.

Why Two Tuples with the Same Values Are Different Objects

a = (1, 2)
b = (1, 2)

print(a == b)    # True  ← same values
print(a is b)    # False ← different objects in memory
Enter fullscreen mode Exit fullscreen mode

Even though they look the same, Python creates two separate tuple objects. They live at different addresses.

But with small integers, Python does something clever:

a = 1
b = 1
print(a is b)    # True ← Python caches small integers!
Enter fullscreen mode Exit fullscreen mode

Python caches integers from -5 to 256 and reuses the same objects — so a and b actually point to the same object. This is a performance optimization called integer interning.


Why Does It Matter? How Python Treats Them Differently

The key difference is what happens in memory:

Mutable (list, dict, set):
a = [1, 2, 3]    object at address 0x100
a.append(4)      SAME object at 0x100, now [1, 2, 3, 4]

Immutable (int, str, tuple):
a = 89           object at address 0x200
a = a + 1        NEW object at 0x300, value is 90
                   old object at 0x200 still exists (until garbage collected)
Enter fullscreen mode Exit fullscreen mode

This affects how you write code — especially when multiple variables point to the same object:

# mutable — shared reference
a = [1, 2, 3]
b = a
b.append(4)
print(a)    # [1, 2, 3, 4] ← a changed too!

# immutable — no shared state issue
a = 89
b = a
b = b + 1
print(a)    # 89 ← a is unchanged
Enter fullscreen mode Exit fullscreen mode

How Arguments Are Passed to Functions

This is where everything comes together. Python passes arguments by object reference — sometimes called "pass by assignment".

What this means:

  • the function receives a reference to the same object
  • whether it can modify the original depends on whether the object is mutable or immutable

Immutable — function cannot change the original:

def add_one(n):
    n = n + 1    # creates a NEW integer, n now points to it
    print(n)     # 90

a = 89
add_one(a)
print(a)         # 89 ← unchanged! function got its own copy
Enter fullscreen mode Exit fullscreen mode

Inside the function, n starts pointing to the same object as a. But when you do n = n + 1, a new integer is created and n now points to that. The original a is untouched.

Mutable — function CAN change the original:

def add_item(my_list):
    my_list.append(4)    # modifies the SAME list object

a = [1, 2, 3]
add_item(a)
print(a)                 # [1, 2, 3, 4] ← changed!
Enter fullscreen mode Exit fullscreen mode

The function receives a reference to the same list. When it calls .append(), it modifies that same object — so the change is visible outside the function too.

The Gotcha — Reassigning Inside a Function:

def replace_list(my_list):
    my_list = [10, 20, 30]   # creates NEW list, local variable only
    print(my_list)            # [10, 20, 30]

a = [1, 2, 3]
replace_list(a)
print(a)                     # [1, 2, 3] ← unchanged!
Enter fullscreen mode Exit fullscreen mode

Reassigning my_list inside the function just makes the local variable point to a new list. The original a still points to the old list.


Summary

Mutable Immutable
Examples list, dict, set int, str, tuple
Can change in place? ✅ Yes ❌ No
+= behavior modifies same object creates new object
Same address after change? ✅ Yes ❌ No
Function can modify original? ✅ Yes ❌ No

The golden rules:

  • == checks if values are equal
  • is checks if they are the literally same object in memory
  • mutable objects can be changed in place — watch out for shared references
  • immutable objects always create new objects when "changed"
  • functions can modify mutable arguments but not immutable ones

Once these click, a huge chunk of Python's behavior that seemed random starts making complete sense.

Top comments (0)