跪拜 Guibai
← All articles
Python

Python OOP: Where Composition Beats Inheritance

By vipxieliang ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

MOST Python codebases make the inheritance-versus-composition decision early and live with the consequences; this material gives working developers a concrete decision framework and shows exactly when subclass explosion and fragile base classes start to hurt.

Summary

Python's object-oriented toolkit gets a full treatment: class and instance semantics, the role of self, instance versus class attributes, the three method kinds, property-based encapsulation, inheritance with super(), MRO for multiple inheritance, duck typing and abstract base classes, and finally a strong push toward composition.

The guide walks through each concept with runnable snippets, then pivots to design guidance. Inheritance hierarchies become fragile and hard to follow once they get deep, and multiple inheritance makes method resolution unpredictable. Composition, demonstrated with a Car holding an Engine and Wheels, and a Logger accepting different writer strategies, lets each dimension vary independently without subclass explosion.

The closing decision table is the practical takeaway: use inheritance only for genuine is-a relationships with stable parent design; prefer composition for code reuse, runtime behavior swaps, and interface definition via ABCs or protocols.

Takeaways
— In Python, `self` refers to the current instance; calling `d1.bark()` executes `Dog.bark(d1)`.
— Class attributes are shared across all instances, while instance attributes are independent per instance; assigning through an instance creates a new attribute that shadows the class attribute.
— `@classmethod` receives the class as `cls` and is commonly used for alternative constructors; `@staticmethod` is just a function in the class namespace.
— Python has no true private members; `_name` denotes protected and `__name` triggers name mangling.
— `@property` with a setter allows validated attribute assignment, raising an exception on invalid input.
— A subclass can override parent methods and reuse parent logic via `super()`.
— Multiple inheritance uses C3 linearization to determine MRO; in the example, class `D(B, C)` resolves methods as `B -> C -> A`.
— Duck typing lets any object with the required method be passed to a function, regardless of inheritance; missing methods raise `AttributeError` at call time.
— Abstract base classes (ABCs) force implementers to define required methods; ABC instances cannot be instantiated.
— Composition expresses has-a relationships by holding objects as attributes and delegating to them, which keeps the containing class largely unchanged when swapping implementations.
— Using inheritance to reuse code leads to deep hierarchies, fragile base class problems, and complex MRO; composition avoids those by letting each dimension vary independently.
— The selection rule: inheritance for clear is-a relationships with stable parents; composition for simple reuse, runtime switching via dependency injection, and interface definition via ABCs/protocols.
Conclusions

The article's logging example is the strongest argument for composition: with inheritance alone, adding formatting and filtering dimensions would force a combinatorial explosion of subclasses, while composition lets each concern be swapped independently.

The MRO walkthrough shows that `super()` in a diamond hierarchy isn't merely calling the immediate parent; it follows the full C3 linearized chain, which trips up many developers who expect a strict depth-first order.

The guide treats duck typing as an inherent advantage of dynamic languages, but the tradeoff is implicit: if an object lacks the method, the failure surfaces only at call time, which is why abstract base classes exist as a constraint mechanism.

The decision table offers a clear rule, but the real-world application often gets murkier: many "is-a" relationships are actually "has-a" in disguise, and a stable parent design is harder to guarantee than it sounds.

Concepts & terms
MRO (Method Resolution Order)
The order in which Python looks up methods in a class hierarchy, determined by the C3 linearization algorithm. It defines which class's method is called in multiple inheritance and how `super()` chains work.
Duck typing
A programming style where an object's suitability is determined by the presence of certain methods or attributes, rather than by its type. If it walks like a duck and talks like a duck, it's treated as a duck.
Name mangling
Python's mechanism for making double-underscore-prefixed attributes (`__name`) harder to access from outside the class. The attribute is internally renamed to `_ClassName__name`.
Composition over inheritance
A design principle that favors building classes by holding other objects and delegating to them (has-a) over extending classes through inheritance (is-a), especially when code reuse is the main goal.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗