
OOP in python - The pythonic way
🧠 Summary
A personal take on how I moved from C++ to Python, and how my mindset around object-oriented programming had to shift. Less boilerplate, more expressiveness — but also some traps if you bring old habits.
✍️ Coming from C/C++
If you're just like me and you learned to code Assembly, C and especially C++, you're probably going to have some changes when joining Python. This is because Python adds a lot of conventions instead of rigid structures. What does it mean? No more declaring variables, no private and public segregation, no getters and setters unless specifically needed.
Been there, done that
Take for an example the following Animal class. It has a getter and a setter for a private member, and explicitly declare that the animal class has a private member called name, with the type std::str.
class Animal {
private:
std::string name; // This declares the name variable
public:
Animal(std::string n) {
name = n;
}
std::string getName() {
return name;
}
};
🐍 The pythonic way
Now in python, you can do the same, but you don't need to. Pay attention to the underscores before the name
variable. That is a python convention that means private. You can do everything with the variable still, but that would mean that you're possibly breaking something as the owner indicated—through the use of underscores—that it is private so it doesn't get modified.
class Animal {
def __init__(self, name: str)
self.__name__ = name # This will Declare and Initialize the name private variable at the same time
}
animal = Animal(name="Lion")
# returns the private name variable
animal.__name__
# sets the private name variable
animal.__name__ = "Tiger"
You can also see that we didn't need to declare our variables in the top of the class like we did in C++. That is because in python, you can declare and initialize a variable at the same time. It will also take the typing—if you're working with typed python—right from the value you are initializing it with.
Way less boilerplate and more flexibility
class Animal:
def __init__(self, name):
self.name = name
@dataclass
class Animal:
name: str
age: int = 0
lion = Animal(name="Lion")
With the use of the @dataclass
decorator, it adds boilerplate methods like:
__init__:
For initializing instance attributes based on type-hinted fields.__repr__:
For creating a readable string representation of the object.__eq__:
For comparing two dataclass instances for equality based on their field values.__hash__:
(If unsafe_hash=True or all fields are hashable) Enables use as dictionary keys or in sets.__lt__, __le__, __gt__, __ge__:
(If order=True) For rich comparisons.
Another thing that really changes the game is duck typing. It allows you to call functions and Properties even if you're not using the same class. It will be clearer in the example below:
@dataclass
class Duck:
name: str
age: int
def make_sound(self):
print("Quack 🦆")
@dataclass
class Cat:
name: str
age: int
def make_sound(self):
print("Meow 🐈")
def animal_sound(animal):
animal.make_sound()
cat = Cat("Elizabeth", 3)
duck = Duck("Robson", 2)
animal_sound(cat) # Prints "Meow 🐈"
animal_sound(duck) # Prints "Quack 🦆"
We rely in the fact that both classes have the same method and we call it. Of course that's not very good but it also works with typed python.
🎯 Key Pythonic Principles
- No private attributes by default – Use _ or __ only when needed
- Properties > Getters/Setters – use @property instead of get_/set_
- Use dataclass for simple containers
- Dunder methods (str, repr, eq) matter for readable, idiomatic code
- Type hints are optional, but good for readability
❌ Anti-patterns from C/C++
Treating python like C++ is Overengineering:
class Rectangle:
def __init__(self, width, height):
self.__width = width
self.__height = height
def get_width(self):
return self.__width
You're guilty of:
- Manual getter/setters
- Treating Python like Java/C++
🧠 Final Thought
Switching to Python isn’t just about syntax — it’s about trusting simplicity. Pythonic OOP is less about control and more about clarity. Sometimes, fewer lines are the more robust solution.