Python Attributes: Understanding How They Work 🐍

Attributes are the foundation of Python's object model, but most beginners (and even many mid-level developers) use them intuitively without understanding what happens under the hood. The result is unpredictable code behavior, memory bloat, and spaghetti APIs.

Let's dive into the nuances 👇

1️⃣ Method Resolution Order (MRO)
Python follows the principle: object first, then class.

When you access obj.attr, the interpreter first looks in obj.__dict__. If not found, it goes to Class.__dict__.
If you accidentally assign something to obj.attr, you create an instance attribute that shadows the class attribute.

class Hero:
weapon = "Sword"

h = Hero()
h.weapon = "Gun" # Now h has its own weapon, while the class still has a sword

This is not a bug, it's a feature, but control it. If you want to change an attribute for all instances, modify the class.

2️⃣ __dict__ vs __slots__
By default, every object has __dict__ — a hash table (dictionary) storing attributes. This is flexible but memory-heavy.

If you create millions of objects of the same type, use __slots__.
🔵What it does: It prevents the creation of __dict__ for instances, fixing the set of attributes in memory (like an array).
🔵Benefit: 25-30% less memory consumption and slightly faster access.
🔵Cost: You lose the dynamic ability to add new attributes on the fly.

3️⃣ The Illusion of Encapsulation
Python has no "private" fields. At all.
Constructs like __attribute are not data protection but name mangling. Python simply renames the variable to _ClassName__attribute to avoid accidental overriding in subclasses.
If you use __ hoping no one will access your data, anyone who knows dir() can see everything. Using _private (single underscore) means "don't touch, it's internal API." It's a matter of culture, not the compiler.

Want to hide data? Use a single underscore _ only as a signal to colleagues: "Don't touch this, it's internal."

4️⃣ @property: Ethical Getter
Never write Java-style getters in Python (get_value(), set_value()). If you need logic when accessing an attribute, use @property.
@property allows you to turn a method into an attribute. You keep a clean API (dot access), but under the hood you can validate data, log access, or compute values on the fly.
class DataStorage:
def __init__(self, value):
self._value = value

@property
def value(self):
return self._value

@value.setter
def value(self, new_val):
if not isinstance(new_val, int):
raise ValueError("Only int, baby")
self._value = new_val


This way you keep a clean interface: obj.value = 10 looks as simple as working with a regular field, but you control what gets written.

What other "magic" places in Python raise questions for you? Drop them in the comments, we'll discuss. 👇

#python_anatomy