| Technique | Benefit | When to use |
|------------------------------------|-------------------------------------------|------------------------------------------|
| __slots__ | Reduces memory, faster attribute access | Many instances, fixed attributes |
| @dataclass(frozen=True) | Immutable, auto __init__, __repr__ | Data containers without logic |
| weakref for cycles | Prevents memory leaks | Observer patterns, caches |
| __new__ override | Control instance creation (e.g., singleton)| Rare; use module‑level global instead |
Python does not have private, protected, or public keywords like Java/C++. Instead, it relies on convention and name mangling.
ABCs define interfaces. They are not for performance; they are for documentation and runtime checking.
from abc import ABC, abstractmethodclass Drawable(ABC): @abstractmethod def draw(self, canvas): pass
@classmethod def __subclasshook__(cls, C): # Allow duck typing: any class with draw() is a Drawable if any("draw" in B.__dict__ for B in C.__mro__): return True return NotImplementedclass Circle: # No explicit inheritance def draw(self, canvas): print(f"Circle on canvas") python 3 deep dive part 4 oop high quality
print(issubclass(Circle, Drawable)) # True (thanks to subclasshook)
Built-in ABCs:
Real-world use: Define a plugin system where third-party code must implement certain methods. Register virtual subclasses with @Drawable.register. | Technique | Benefit | When to use
By default, == checks for value equality (calling __eq__), while is checks for identity (memory address via id()).
a = [1, 2]
b = [1, 2]
print(a == b) # True (Values are equal)
print(a is b) # False (Different objects in memory)
Every Python instance carries a hidden __dict__ (a hash table for attributes). This is flexible but memory-inefficient for millions of objects.
Enter __slots__: A declaration that freezes attribute names, replacing __dict__ with a C array.
Before – Profligate:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
In languages like Java, private attributes are accessed via getters/setters. In Python, we start with public attributes and refactor to properties when needed.
Bad (Java-style):
class BadCircle:
def __init__(self, radius):
self._radius = radius
def get_radius(self):
return self._radius
def set_radius(self, value):
if value < 0:
raise ValueError("Radius cannot be negative")
self._radius = value
Good (Pythonic):
class Circle:
def __init__(self, radius):
self.radius = radius # Uses setter if defined
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("Radius cannot be negative")
self._radius = value
@property
def area(self):
return 3.14159 * self._radius ** 2
Key insight: Properties allow you to evolve an attribute into logic without changing the API. Your users still write circle.radius = 5, not circle.set_radius(5).