Python 3 Deep Dive Part 4 Oop High Quality ◆
Let’s combine __slots__, property, descriptors, and __init_subclass__ into a small data validation framework:
class Validator: def __set_name__(self, owner, name): self.name = namedef __get__(self, obj, objtype=None): return obj.__dict__.get(self.name) def __set__(self, obj, value): self.validate(value) obj.__dict__[self.name] = value def validate(self, value): raise NotImplementedErrorclass Positive(Validator): def validate(self, value): if value <= 0: raise ValueError(f"self.name must be positive")
class NonEmptyString(Validator): def validate(self, value): if not isinstance(value, str) or len(value.strip()) == 0: raise ValueError(f"self.name must be non-empty string")
class Model: def init_subclass(cls, **kwargs): super().init_subclass(**kwargs) if not hasattr(cls, 'slots'): cls.slots = tuple()
def __repr__(self): attrs = k: getattr(self, k) for k in self.__slots__ if hasattr(self, k) return f"self.__class__.__name__(attrs)"class Product(Model): slots = ('name', 'price') name = NonEmptyString() price = Positive()
p = Product() p.name = "Laptop" p.price = 999 print(p) # Product('name': 'Laptop', 'price': 999)
This pattern gives validation, memory efficiency, and clean introspection — a practical blend of several deep OOP features. python 3 deep dive part 4 oop high quality
class Engine: def start(self): ...
class Car: def init(self, engine: Engine): self._engine = engine def drive(self): self._engine.start()
The magic of Python objects lies in "dunder" (double underscore) methods. These allow objects to integrate seamlessly with the language's syntax.
__init__ initializes an already-created instance. __new__ actually creates the instance. It’s a static method (though not decorated as such) that receives the class and returns a new instance.
Use case examples:
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
Welcome to Part 4 of our Python 3 Deep Dive series. If you’ve made it this far, you already understand control flow, functions, and iterables. Now, it’s time to tackle the paradigm that separates “scripting” from “software engineering”: Object-Oriented Programming (OOP).
But this is not your average “classes and objects” tutorial. This is a high-quality deep dive. We will explore Python’s OOP internals, metaclasses, descriptors, advanced method resolution, and the subtle patterns that lead to maintainable, production-grade code. Let’s combine __slots__ , property , descriptors, and
Python does not have true private attributes. Instead:
class Secret:
def __init__(self):
self.__code = 123
s = Secret()
print(s.__code) # AttributeError
print(s._Secret__code) # 123 — still accessible!
Name mangling exists primarily to avoid accidental overrides in subclasses, not to enforce privacy.
Python’s ethos: “We’re all consenting adults.” Use _ and trust others to respect it.
A metaclass is to a class what a class is to an instance. The default metaclass is type.
def my_meta(name, bases, dct):
dct['version'] = 1.0
return type(name, bases, dct)
class MyClass(metaclass=my_meta):
pass
print(MyClass.version) # 1.0
Practical use: Singleton metaclass:
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class Database(metaclass=SingletonMeta):
pass
When to use metaclasses (rarely!):
High-quality rule: If you’re unsure, don’t use metaclasses. They make code magical and hard to debug. Prefer class decorators. class Product(Model): slots = ('name', 'price') name =
| Anti-Pattern | Why It’s Bad | High-Quality Alternative |
|--------------|---------------|---------------------------|
| God Object | One class does everything | SRP + decomposition |
| Circular imports | Two classes import each other | Move shared code to a third module |
| Overusing inheritance | “Is-a” forced where “has-a” fits | Composition |
| Mutable defaults | def __init__(self, items=[]) | def __init__(self, items=None) |
| Using __slots__ prematurely | Optimizes nothing, restricts future | Only after profiling memory |