Encapsulation and Inheritance

Encapsulation hides the internal details of an object, exposing only safe operational interfaces. Inheritance lets one class automatically have the functionality of another, avoiding repetition. These two concepts are the backbone of object-oriented programming.


1. Encapsulation and Private Attributes

Encapsulation means that an object's internal data should not be directly accessed from outside — it should be manipulated through methods.

Single Underscore _ — Convention for "Protected" Attributes

Example: Single and Double Underscore

PYTHON
class Person:
    def __init__(self, name, age):
        self.name = name
        self._age = age      # Single underscore: hint that it's internal

p = Person("Zhang San", 25)
print(p.name)               # Zhang San — public attribute
print(p._age)               # 25 — accessible, but not recommended
▶ Try it Yourself

A single underscore is just a convention, not enforced — Python won't stop you from accessing it. It's like writing in the documentation "this is internal, don't touch."

Double Underscore __ — Name Mangling

Example: Bank Account Class

PYTHON
class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner
        self.__balance = balance    # Double underscore: private attribute
    
    def get_balance(self):
        """Safely get balance"""
        return self.__balance
    
    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
            return True
        return False

account = BankAccount("Zhang San", 10000)
print(account.owner)                # Zhang San — public
# print(account.__balance)          # Error! AttributeError
print(account.get_balance())        # 10000 — accessed via method
▶ Try it Yourself

Double underscores trigger name mangling__balance is internally transformed to _BankAccount__balance. This isn't true "privacy," just making accidental overriding less likely.

Tip: Python has no true private. It relies on the "we are all consenting adults" understanding — single underscore means "internal use," double underscore prevents name collisions. Other languages (like Java) have a true private keyword; Python doesn't and isn't planning to add one.

Example: Temperature Class (Difficulty: Star-Star)

PYTHON
class Temperature:
    def __init__(self, celsius=0):
        self.__celsius = celsius
    
    def to_fahrenheit(self):
        return self.__celsius * 9 / 5 + 32
    
    def set_celsius(self, value):
        """Safely set temperature — cannot go below absolute zero"""
        if value < -273.15:
            print("Temperature cannot be below absolute zero!")
            return
        self.__celsius = value
    
    def get_celsius(self):
        return self.__celsius

t = Temperature(100)
print(f"100°C = {t.to_fahrenheit():.1f}°F")   # 212.0°F

t.set_celsius(-300)      # Temperature cannot be below absolute zero!
t.set_celsius(0)
print(f"0°C = {t.to_fahrenheit():.1f}°F")     # 32.0°F
▶ Try it Yourself

2. @property: Elegant Access Control

Writing set_celsius() / get_celsius() is verbose. @property lets you control access while keeping the syntax of attribute access:

Example: Using @property

PYTHON
class Temperature:
    def __init__(self, celsius=0):
        self._celsius = celsius
    
    @property
    def celsius(self):
        """Get Celsius temperature"""
        return self._celsius
    
    @celsius.setter
    def celsius(self, value):
        """Set Celsius temperature — auto-validate"""
        if value < -273.15:
            raise ValueError("Temperature cannot be below absolute zero!")
        self._celsius = value
    
    @property
    def fahrenheit(self):
        """Fahrenheit (read-only — no setter)"""
        return self._celsius * 9 / 5 + 32

t = Temperature(100)
print(t.celsius)                    # 100 — accessed like an attribute
print(t.fahrenheit)                 # 212.0

t.celsius = 0                       # Assigned like an attribute
print(t.celsius)                    # 0

# t.celsius = -300                  # Would raise ValueError!
# t.fahrenheit = 100                # Error! No setter
▶ Try it Yourself

Tip: Advantages of @property: 1) No parentheses needed for access: t.celsius instead of t.get_celsius(). 2) Automatic validation on assignment: t.celsius = value instead of t.set_celsius(value). 3) Can convert a regular attribute to a computed one without breaking existing code.


3. Inheritance

Inheritance lets a child class have all the attributes and methods of its parent class:

Example: Animal Inheritance

PYTHON
class Animal:
    """Animal base class"""
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        return "..."
    
    def eat(self, food):
        return f"{self.name} is eating {food}"

class Dog(Animal):
    """Dog — inherits from Animal"""
    def speak(self):
        return "Woof!"

class Cat(Animal):
    """Cat — inherits from Animal"""
    def speak(self):
        return "Meow!"

dog = Dog("Wangcai")
cat = Cat("Mimi")

print(dog.speak())                  # Woof! (overrides parent method)
print(cat.speak())                  # Meow! (overrides parent method)
print(dog.eat("bone"))              # Wangcai is eating bone (inherited from parent)
print(cat.eat("fish"))              # Mimi is eating fish (inherited from parent)
▶ Try it Yourself

super(): Calling Parent Methods

Example: super() to Call the Parent

PYTHON
class Animal:
    def __init__(self, name):
        self.name = name

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)      # Call the parent's __init__
        self.breed = breed          # Add child-specific attributes
    
    def introduce(self):
        return f"I'm {self.name}, a {self.breed}"

dog = Dog("Wangcai", "Golden Retriever")
print(dog.introduce())              # I'm Wangcai, a Golden Retriever
▶ Try it Yourself

4. Mixin Pattern

A Mixin is a special class — not used independently, but "mixed in" to add functionality to other classes:

Example: Mixin Pattern

PYTHON
class FlyMixin:
    """Flying ability mixin"""
    def fly(self):
        return f"{self.name} is flying!"

class SwimMixin:
    """Swimming ability mixin"""
    def swim(self):
        return f"{self.name} is swimming!"

class Animal:
    def __init__(self, name):
        self.name = name

class Duck(Animal, SwimMixin, FlyMixin):
    """Duck — can both swim and fly"""
    def speak(self):
        return "Quack!"

class Fish(Animal, SwimMixin):
    """Fish — can only swim"""
    def speak(self):
        return "..."

duck = Duck("Donald")
fish = Fish("Nemo")

print(duck.speak())                 # Quack!
print(duck.swim())                  # Donald is swimming!
print(duck.fly())                   # Donald is flying!
print(fish.swim())                  # Nemo is swimming!
▶ Try it Yourself

Tip: Mixin naming convention: Class names ending in Mixin indicate they are for mixing in, not for standalone use. A class can mix in multiple Mixins plus one main base class — much more flexible than deep single inheritance.


Common Use Cases


FAQ

Q When should I use inheritance vs. Mixin?
A Inheritance suits "is-a" relationships — a dog is an animal. Mixins suit "has-a ability" — a duck can swim. Prefer composition (Mixin) over inheritance, as deep inheritance hierarchies are hard to maintain. A good rule: inheritance should not exceed 3 levels.
Q What's the difference between @property and a regular method?
A @property makes a method look like an attribute — no parentheses for access, assignment triggers the setter. It's essentially an "attribute interceptor" — you write code as if manipulating an attribute, but a method actually executes.
Q Does Python support multiple inheritance? Any pitfalls?
A Yes — class C(A, B). However, multiple inheritance has the "diamond problem" (which parent method to use when both have the same name). Python uses MRO (Method Resolution Order), searching left to right. Mixins are a design pattern that leverages multiple inheritance, with Mixin classes being small and functionally independent, avoiding diamond issues.

Summary


Exercises

  1. Beginner (Difficulty: Star): Define a Product class with @property for the price attribute — must be positive when setting; returns the original value when getting.

  2. Intermediate (Difficulty: Star-Star): Define a three-level inheritance: Animal -> Mammal -> Dog / Cat. Animal has name and age; Mammal adds fur_color; Dog and Cat each implement their own speak() method.

  3. Advanced (Difficulty: Star-Star-Star): Create a logging system with LoggableMixin (provides log_info(), log_error() methods) and a base class Application (has a name attribute). Have WebApp and CLIApp both inherit from Application and mix in LoggableMixin to implement logging functionality.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏