Python Tutorial / Reuse Code with Inheritance and Composition
Inheritance extends a class relationship and its behavior; composition uses another object as a component. Choose based on substitutability and replaceable collaborators, rather than merely noticing similar code.
Judge the relationship and direction of change
Inheritance is useful when a child can honor the contract expected of its parent. A ledger uses a fee policy; it is not itself a kind of fee policy, so composition expresses that relationship naturally. Deep inheritance hierarchies can spread the impact of changes.
super() delegates to the next implementation in the method resolution order rather than permanently naming one parent. Start with a simple structure before relying on more complex multiple-inheritance behavior.
For the relevant APIs and platform requirements, see Python: classes.
Object responsibilities and policy tests build on the rules demonstrated in Group State and Behavior with Classes and Instances and Write Automated Tests with unittest and Boundary Cases.
Run a replaceable fee policy
Save the complete code below as composition.py in a separate practice folder, using UTF-8. Run python composition.py in a terminal with your project virtual environment selected. Outside a virtual environment on macOS or Ubuntu, use python3 when that is your interpreter command.
class FeePolicy:
def apply(self, amount):
return amount
class FixedFee(FeePolicy):
def __init__(self, fee):
self.fee = fee
def apply(self, amount):
return super().apply(amount) + self.fee
class Ledger:
def __init__(self, policy):
self.policy = policy
def charge(self, amount):
return self.policy.apply(amount)
print(Ledger(FeePolicy()).charge(8000))
print(Ledger(FixedFee(500)).charge(8000))
Expected output:
8000 8500
Identify inheritance and delegation in the example
FixedFee keeps FeePolicy’s apply interface and changes the result calculation. Ledger stores a policy and delegates to it. You can change the policy without changing ledger code, and tests can supply a small fake policy.
Python does not require every cooperating object to inherit from the same parent. Another object exposing an appropriate apply method can work too. Document the required behavior or express it through type hints so that compatibility is more than a matching method name.
Compare the same requirement—apply a fee policy to an amount—using two alternatives. Inheritance overrides fee; composition receives a callable fee_policy. Both produce 8,500 below, then replacing only the composed object’s policy produces 8,000. Composition suits runtime policy replacement; inheritance can suit a stable subtype relationship. A design can combine both, but this comparison isolates the decision.
Save the complete example as policies.py and run python policies.py in the same practice directory.
class InheritedLedger:
def fee(self):
return 0
def charge(self, amount):
return amount + self.fee()
class FixedLedger(InheritedLedger):
def fee(self):
return 500
class ComposedLedger:
def __init__(self, fee_policy):
self.fee_policy = fee_policy
def charge(self, amount):
return amount + self.fee_policy()
def fixed_fee():
return 500
print(FixedLedger().charge(8000))
ledger = ComposedLedger(fixed_fee)
print(ledger.charge(8000))
ledger.fee_policy = lambda: 0
print(ledger.charge(8000))
Expected output:
8500 8500 8000
Review the contract before adding a hierarchy
- Does the child require stricter inputs or change the meaning of the parent’s result?
- Are you inheriting unrelated behavior merely to reuse one method?
- Would accepting a replaceable component make the design simpler?
Change the fixed fee to zero and confirm the same result as the basic policy, then restore it. Keep input/output outside the policy so that the calculation remains easy to compare. For a small problem, a function may be sufficient; an object hierarchy should earn its complexity.




