Intermediate Python: Practical OOP – The Code and Commands That Really Matter
Intermediate Python OOP: the essentials in one article — real code, diagrams and concrete steps, excerpts from a 36-lesson course.
No endless theory here: open the terminal and practice. Here's the essentials of Intermediate Python OOP, extracted directly from a complete 36-lesson course — with real code you can copy-paste right now.
- Introduction and Installation
- Python Recap
- Advanced Functions and Lambdas
- Classes and Objects
- Inheritance and Polymorphism
Encapsulation, Properties and Setters
Learning Objectives
_x and __x conventions, turn an attribute into a property to add validation, and know when to do so.What is Encapsulation?
Encapsulation means hiding the internal details of a class and exposing only a controlled interface.
Python Conventions
Python does not enforce privacy (unlike Java). It relies on conventions based on naming.
| Convention | Meaning | Actual Effect |
|---|---|---|
nom | Public | Free access, part of the public API |
_nom | Protected (convention) | "Do not touch unless you know what you're doing" |
__nom | Private (name mangling) | Renamed to _ClassName__nom |
nom_ | Avoids keyword collision | e.g. class_ instead of class |
class CompteBancaire:
def __init__(self, titulaire, solde):
self.titulaire = titulaire # public
self._solde = solde # convention: do not touch
self.__pin = "1234" # private (name mangling)
c = CompteBancaire("Alice", 1000)
print(c.titulaire) # OK
print(c._solde) # accessible but discouraged
# print(c.__pin) # AttributeError !
print(c._CompteBancaire__pin) # accessible via the mangled name__ is not security — It is a mechanism to avoid accidental collisions during inheritance. For real security, use system-level permissions.The Problem Without Encapsulation
class Temperature:
def __init__(self, celsius):
self.celsius = celsius
t = Temperature(25)
t.celsius = -500 # absurd!
print(t.celsius) # -500No validation. -500 is impossible (absolute zero = -273.15) but Python accepts it.
@property: Turning an Attribute into a Method
A @property makes a call like obj.attribute actually execute a method without changing the usage syntax.
class Temperature:
def __init__(self, celsius):
self._celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, valeur):
if valeur < -273.15:
raise ValueError("Below absolute zero, impossible")
self._celsius = valeur
t = Temperature(25)
print(t.celsius) # 25 (calls the getter)
t.celsius = 30 # calls the setter, OK
t.celsius = -300 # ValueError!t.celsius syntax but add invisible control. User code does not need to change.Read-Only Property
For a computed (derived) attribute that should not be modifiable directly.
class Cercle:
def __init__(self, rayon):
self.rayon = rayon
@property
def diametre(self):
return self.rayon * 2
@property
def aire(self):
from math import pi
return pi * self.rayon ** 2
c = Cercle(5)
print(c.diametre) # 10
print(c.aire) # 78.539...
# c.aire = 100 # AttributeError: no setterFull Property with Getter, Setter, Deleter
class Personne:
def __init__(self, nom):
self._nom = nom
@property
def nom(self):
return self._nom
@nom.setter
def nom(self, valeur):
if not isinstance(valeur, str) or len(valeur) < 2:
raise ValueError("Invalid name")
self._nom = valeur.title() # normalization
@nom.deleter
def nom(self):
print("Deleting name")
self._nom = None
p = Personne("alice")
print(p.nom) # Alice (normalized)
p.nom = "bob"
print(p.nom) # Bob
del p.nom # Deleting namePractical Case: CompteBancaire with Validation
class CompteBancaire:
def __init__(self, titulaire, solde=0):
self.titulaire = titulaire
self._solde = 0
self.solde = solde # goes through the setter!
@property
def solde(self):
return self._solde
@solde.setter
def solde(self, valeur):
if valeur < 0:
raise ValueError("Negative balance forbidden")
self._solde = valeur
def deposer(self, montant):
if montant <= 0:
raise ValueError("Amount must be positive")
self.solde += montant # goes through the setter
def retirer(self, montant):
if montant > self._solde:
raise ValueError("Insufficient balance")
self.solde -= montant
c = CompteBancaire("Alice", 1000)
c.deposer(500)
print(c.solde) # 1500
# c.solde = -100 # ValueErrorSimple Inheritance
Learning Objectives
super().The Concept
Inheritance lets you create a specialized class from a general class. The child class receives everything the parent class offers, then adds or modifies behavior.
Dog is-a Animal.First Example
class Animal:
def __init__(self, nom, age):
self.nom = nom
self.age = age
def manger(self):
print(f"{self.nom} eats")
def dormir(self):
print(f"{self.nom} sleeps")
class Chien(Animal):
def aboyer(self):
print(f"{self.nom}: Woof!")
rex = Chien("Rex", 5)
rex.manger() # inherited from Animal
rex.dormir() # inherited from Animal
rex.aboyer() # specific to Chien
print(rex.age) # inheritedChien does not define __init__: Python automatically uses the one from Animal.
Overriding a Method
The child class can redefine a parent method to adapt its behavior.
class Animal:
def parler(self):
print("Generic animal sound")
class Chien(Animal):
def parler(self):
print("Woof!")
class Chat(Animal):
def parler(self):
print("Meow")
for a in [Chien(), Chat(), Animal()]:
a.parler()
# Woof!
# Meow
# Generic animal sounda.parler() produces a different result depending on the actual class of a. This is one of the 4 pillars of OOP.super(): Calling the Parent Method
When overriding a method, you often want to extend the parent behavior rather than replace it.
class Animal:
def __init__(self, nom, age):
self.nom = nom
self.age = age
def description(self):
return f"{self.nom}, {self.age} years old"
class Chien(Animal):
def __init__(self, nom, age, race):
super().__init__(nom, age) # calls Animal.__init__
self.race = race
def description(self):
base = super().description() # calls Animal.description
return f"{base}, breed {self.race}"
rex = Chien("Rex", 5, "Labrador")
print(rex.description()) # Rex, 5 years old, breed Labrador__init__, you must call super().__init__(...) to initialize the parent's attributes. Skipping it = guaranteed bugs.Checking Parentage: isinstance and issubclass
rex = Chien("Rex", 5, "Labrador")
print(isinstance(rex, Chien)) # True
print(isinstance(rex, Animal)) # True (inheritance)
print(isinstance(rex, Chat)) # False
print(issubclass(Chien, Animal)) # True
print(issubclass(Animal, Chien)) # Falseisinstance(x, Animal) rather than type(x) == Animal. The first form respects inheritance.When to Use Inheritance?
| Relationship | Inheritance? |
|---|---|
| Dog is-an Animal | Yes |
| Novel is-a Book | Yes |
| Car has-a Engine | No — composition |
| Library contains Books | No — composition |
Concrete Case: Library Application
class Livre:
def __init__(self, titre, auteur, isbn):
self.titre = titre
self.auteur = auteur
self.isbn = isbn
self.disponible = True
def description(self):
return f"{self.titre} by {self.auteur}"
class Roman(Livre):
def __init__(self, titre, auteur, isbn, genre):
super().__init__(titre, auteur, isbn)
self.genre = genre
def description(self):
return f"{super().description()} — {self.genre} novel"
class LivreNumerique(Livre):
def __init__(self, titre, auteur, isbn, format_fichier):
super().__init__(titre, auteur, isbn)
self.format_fichier = format_fichier
def telecharger(self):
print(f"Downloading {self.titre}.{self.format_fichier}")
dune = Roman("Dune", "Herbert", "978...", "science-fiction")
ebook = LivreNumerique("1984", "Orwell", "978...", "epub")
print(dune.description()) # Dune by Herbert — science-fiction novel
print(ebook.description()) # 1984 by Orwell
ebook.telecharger() # Downloading 1984.epubCommon Hierarchies in Python
Lambda, map, filter, reduce
lambda) and higher-order functions (map, filter, reduce), which let you transform and filter data very concisely.Learning Objectives
lambda and how to choose between map/filter and a list comprehension depending on context.What is a lambda?
A lambda is an anonymous function (no name) that fits on a single line. It is used for short operations passed as arguments to another function.
Syntax
lambda parameters: expression
Comparison
def doubler(x):
return x * 2
doubler_lambda = lambda x: x * 2
print(doubler(5)) # 10
print(doubler_lambda(5)) # 10def is like writing a named recipe in a book. A lambda is scribbling a recipe on a sticky note you hand over immediately.Multi-parameter Lambdas
somme = lambda a, b: a + b print(somme(3, 4)) # 7 est_pair = lambda n: n % 2 == 0 print(est_pair(8)) # True
return, no multiple lines. As soon as things get more complex, use def.map(): Transforming Each Element
map(function, iterable) applies the function to every element and returns an iterator. It is often wrapped in list().
prix = [10, 25, 7, 50] prix_ttc = list(map(lambda p: p * 1.20, prix)) print(prix_ttc) # [12.0, 30.0, 8.4, 60.0] mots = ["python", "poo", "code"] majuscules = list(map(str.upper, mots)) print(majuscules) # ['PYTHON', 'POO', 'CODE']
With Multiple Sequences
a = [1, 2, 3] b = [10, 20, 30] sommes = list(map(lambda x, y: x + y, a, b)) print(sommes) # [11, 22, 33]
filter(): Keeping What Passes the Test
notes = [12, 8, 15, 6, 18, 11] reussies = list(filter(lambda n: n >= 10, notes)) print(reussies) # [12, 15, 18, 11] mots = ["chat", "chien", "ours", "lion"] courts = list(filter(lambda m: len(m) <= 4, mots)) print(courts) # ['chat', 'ours', 'lion']
This article covers the most useful excerpts — the complete Intermediate Python OOP course (11 chapters, 36 lessons, corrected exercises and final project) takes you all the way.
./access-the-complete-course free course: Mastering Claude CodeFAQ
How long does it take to learn Intermediate Python OOP?
Are there any prerequisites?
Where to start concretely?
📬 Want to receive this kind of guide every week? Subscribe for free — real code, zero fluff.