Leia a versão em português deste artigo aqui.

1. Introduction

Ever wondered why modifying a list inside a function changes the original list, but modifying a number doesn't? You've just run into one of the most fundamental — and most misunderstood — concepts in Python: its data model.

Understanding how Python represents, identifies, and compares objects isn't just theory: it's what saves you from subtle bugs related to aliasing, incorrect comparisons, and unexpected side effects in mutable containers.

In this first article of the series, we'll build the foundation you need for that: what an object actually is in Python, the difference between identity and equality, what mutability really means, how an object is born and dies, and how all of this connects once objects live inside containers.

This article assumes you're already comfortable with basic Python syntax (variables, lists, dictionaries, functions). If that's you, let's get straight to it.

2. Everything is an object

The data model defines how values are represented and how they behave in Python. Every piece of data your program handles is an object: numbers, strings, lists, functions, classes, and even modules (PYTHON SOFTWARE FOUNDATION, 2026a).

Every Python object has three essential characteristics:

  • Identity: the object's address in memory. Checked with id(object_name).
  • Type: defines which values and operations the object supports. Checked with type(object_name).
  • Value: the content the object represents, which may or may not change depending on the object's mutability.

Type affects nearly every aspect of an object's behavior. That's even true for identity: some types have a single instance shared across the entire program, which is why the correct way to compare them is by identity (is), not equality (==):

  • None represents the absence of a value;
  • NotImplemented signals that a special operation doesn't support the operands it received;
  • ..., also called Ellipsis, can be used as a sentinel and in extended slicing operations.
type(None)            # <class 'NoneType'>
type(NotImplemented)  # <class 'NotImplementedType'>
type(...)             # <class 'ellipsis'>

Enter fullscreen mode Exit fullscreen mode

For single-instance types, is is always the recommended way to check for absence of value:

x ==  None # WRONG
x is None # CORRECT

Enter fullscreen mode Exit fullscreen mode

3. Identity vs Equality

As we just saw, an object's identity refers to its memory address: id(x) returns an int representing the memory location where x is stored. Equality, on the other hand, checks whether the values of two objects are the same, using the magic method __eq__ — we'll dig deeper into that method in upcoming chapters.

  • is: compares identity.
  • ==: compares content or value.

This distinction between identity and equality is the foundation for understanding mutability.

4. Mutable vs Immutable

An object's mutability is determined by its type. A mutable object can have its value changed after creation. An immutable object cannot be modified; any operation on it produces a new object (PYTHON SOFTWARE FOUNDATION, 2026b).

Immutable Mutable
NoneType list
bool dict
int, float, complex set
str, tuple, range bytearray
bytes, frozenset

Mutation changes the object itself; reassignment makes the name point to a new object.

Example with a mutable object:

items = ['a', 'b']  # list is mutable
alias = items
items.append('c')
items  # ['a', 'b', 'c']
alias  # ['a', 'b', 'c']

Enter fullscreen mode Exit fullscreen mode

Example with an immutable object:

total = 10  # int is immutable
alias = total
total += 5  # a new object with value 15 is created; total now points to it
total  # 15
alias  # 10

Enter fullscreen mode Exit fullscreen mode

Immutable objects can hold references to mutable objects, because immutability in Python affects only the object's reference structure, not the content it points to.

record = ("Alyne", ["Python"])
record[1].append("SQL")
record  # ("Alyne", ["Python", "SQL"])

Enter fullscreen mode Exit fullscreen mode

The tuple record still points to the same references — only the inner list was changed.

5. Object life cycle

An object's life cycle in Python has four main phases: creation and memory allocation, attribute initialization, active use throughout the program via references, and destruction with memory release controlled by reference counting and the garbage collector.

import sys

a = [1, 2, 3]
sys.getrefcount(a)  # number of active references to the object (includes getrefcount's own temporary reference)

b = a  # new reference to the same object
sys.getrefcount(a)  # the count goes up, since 'a' and 'b' now point to the same object

Enter fullscreen mode Exit fullscreen mode

When an object becomes unreachable — that is, when its reference count reaches zero — it can be garbage collected by CPython through a reference-counting scheme with delayed detection of cyclically linked garbage. This cyclic collection is optional and doesn't guarantee that all garbage containing circular references will actually be collected.

6. Containers

Container objects are data structures capable of holding other objects — in other words, objects that contain references to others.

They can hold values of different types at the same time, support iteration through loops, and it's common to use the in operator to efficiently check whether an item is present in a container — a check that, internally, relies on equality (==), not identity.

Main container types:

  • Lists (list): ordered, mutable sequences, defined with square brackets [].
  • Tuples (tuple): ordered, immutable sequences, defined with parentheses ().
  • Dictionaries (dict): collections of key-value pairs, defined with curly braces {}.
  • Sets (set): collections of unique, unordered elements, also defined with curly braces {}.

Watch out for a common gotcha: an empty {} creates a dict, not a set. To create an empty set, you need to use set() explicitly.

Since containers hold references, not copies, everything we've covered about identity, equality, and mutability applies directly here: modifying a mutable object inside a container affects every variable that references that same object — even if the container itself is immutable, as we saw with the tuple example in section 4.

7. Conclusion

Identity, equality, mutability, life cycle, and containers aren't isolated topics — they're facets of the same core concept: in Python, variables are references to objects, not boxes holding values. Understanding that is what lets you predict, with confidence, when a change will propagate and when it will produce a brand-new object.

That foundation is what the next step of the series builds on: magic methods (dunder methods), the protocol Python uses behind seemingly simple operations like len(obj), obj[key], or for item in obj. In Part 2, we'll see how these methods let objects you create behave just like the language's built-in types.

References

PYTHON SOFTWARE FOUNDATION. 3. Data model. In: Python 3.14.6 Documentation. [S. l.]: Python Software Foundation, 2026a. Available at: https://docs.python.org/3/reference/datamodel.html. Accessed on: Jul. 28, 2026.

PYTHON SOFTWARE FOUNDATION. Built-in Types. In: Python 3.14.6 Documentation. [S. l.]: Python Software Foundation, 2026b. Available at: https://docs.python.org/3/library/stdtypes.html. Accessed on: Jul. 28, 2026.