Python: 封装与继承

封装把对象的内部细节藏起来,只暴露安全的操作接口。继承让一个类自动拥有另一个类的功能,不用重复写。这两个概念是面向对象编程的中坚力量。


1. 封装与私有属性

封装(Encapsulation)的意思是——对象的内部数据不应该被外部直接访问,而是通过方法来操作。

(1) 单下划线 _ —— 约定为"保护"属性

▶ 示例:单下划线与双下划线

PYTHON
class Person:
    def __init__(self, name, age):
        self.name = name
        self._age = age      # 单下划线:暗示"别动我"

p = Person("Alice", 25)
print(p.name)               # Alice——公开属性
print(p._age)               # 25——能访问到,但建议别这么干
▶ 试一试

输出:

TEXT 📖 仅展示
# 函数定义成功

单下划线只是约定,不是强制——Python 不会阻止你访问。它相当于在文档里写"这是内部属性,别碰"。

(2) 双下划线 __ —— 名称改写(Name Mangling)

▶ 示例:银行账户类

PYTHON
class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner
        self.__balance = balance    # 双下划线:私有属性
    
    def get_balance(self):
        """安全获取余额"""
        return self.__balance
    
    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
            return True
        return False

account = BankAccount("Alice", 10000)
print(account.owner)                # Alice——公开的
# print(account.__balance)          # 报错!AttributeError
print(account.get_balance())        # 10000——通过方法访问
▶ 试一试

输出:

TEXT 📖 仅展示
# 函数定义成功

双下划线触发名称改写——__balance 在内部变成了 _BankAccount__balance。这不是真正的"私有",只是让意外覆盖的可能性更小。

💡 Python 没有真正的私有。 它靠的是"大家都是成年人"的默契——单下划线表示"内部使用",双下划线防止名字冲突。其他语言(如 Java)有真正的 private 关键字,Python 没有也不打算加。

▶ 示例:温度类(难度⭐⭐)

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):
        """安全设置温度——不能低于绝对零度"""
        if value < -273.15:
            print("温度不能低于绝对零度!")
            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)      # 温度不能低于绝对零度!
t.set_celsius(0)
print(f"0°C = {t.to_fahrenheit():.1f}°F")     # 32.0°F
▶ 试一试

输出:

TEXT 📖 仅展示
温度不能低于绝对零度!


2. @property:优雅的访问控制

set_celsius() / get_celsius() 太啰嗦了。@property 让你既能控制访问,又能像访问属性一样自然:

▶ 示例:使用 @property

PYTHON
class Temperature:
    def __init__(self, celsius=0):
        self._celsius = celsius
    
    @property
    def celsius(self):
        """获取摄氏温度"""
        return self._celsius
    
    @celsius.setter
    def celsius(self, value):
        """设置摄氏温度——自动验证"""
        if value < -273.15:
            raise ValueError("温度不能低于绝对零度!")
        self._celsius = value
    
    @property
    def fahrenheit(self):
        """华氏温度(只读——没有 setter)"""
        return self._celsius * 9 / 5 + 32

t = Temperature(100)
print(t.celsius)                    # 100——像属性一样访问
print(t.fahrenheit)                 # 212.0

t.celsius = 0                       # 像属性一样赋值
print(t.celsius)                    # 0

# t.celsius = -300                  # 会触发 ValueError !
# t.fahrenheit = 100                # 报错!没有 setter
▶ 试一试

输出:

TEXT 📖 仅展示
# 函数定义成功
💡 @property 的优势: ① 调用时不需要括号 t.celsius 而不是 t.get_celsius()。② 赋值时自动校验 t.celsius = value 而不是 t.set_celsius(value)。③ 可以在不破坏已有代码的前提下,把普通属性改成需要计算的属性。



3. 继承

继承让子类拥有父类的所有属性和方法:

▶ 示例:动物继承

PYTHON
class Animal:
    """动物基类"""
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        return "..."
    
    def eat(self, food):
        return f"{self.name}在吃{food}"

class Dog(Animal):
    """狗——继承自动物"""
    def speak(self):
        return "汪汪!"

class Cat(Animal):
    """猫——继承自动物"""
    def speak(self):
        return "喵喵~"

dog = Dog("旺财")
cat = Cat("咪咪")

print(dog.speak())                  # 汪汪!(重写了父类方法)
print(cat.speak())                  # 喵喵~(重写了父类方法)
print(dog.eat("骨头"))              # 旺财在吃骨头(继承自父类)
print(cat.eat("鱼"))                # 咪咪在吃鱼(继承自父类)
▶ 试一试

输出:

TEXT 📖 仅展示
# 函数定义成功

(1) super():调用父类方法

▶ 示例:super() 调用父类

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

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)      # 调用父类的 __init__
        self.breed = breed          # 增加子类特有的属性
    
    def introduce(self):
        return f"我是{self.name},一只{self.breed}"

dog = Dog("旺财", "金毛")
print(dog.introduce())              # 我是旺财,一只金毛
▶ 试一试

输出:

TEXT 📖 仅展示
# 函数定义成功


4. Mixin 模式

Mixin 是一种特殊的类——不单独使用,而是通过"混入"给其他类增加功能:

▶ 示例:Mixin 混入模式

PYTHON
class FlyMixin:
    """飞行能力混入"""
    def fly(self):
        return f"{self.name}在飞!"

class SwimMixin:
    """游泳能力混入"""
    def swim(self):
        return f"{self.name}在游泳!"

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

class Duck(Animal, SwimMixin, FlyMixin):
    """鸭子——既会游泳又会飞"""
    def speak(self):
        return "嘎嘎!"

class Fish(Animal, SwimMixin):
    """鱼——只会游泳"""
    def speak(self):
        return "..."

duck = Duck("唐老鸭")
fish = Fish("尼莫")

print(duck.speak())                 # 嘎嘎!
print(duck.swim())                  # 唐老鸭在游泳!
print(duck.fly())                   # 唐老鸭在飞!
print(fish.swim())                  # 尼莫在游泳!
▶ 试一试

输出:

TEXT 📖 仅展示
# 函数定义成功
💡 Mixin 的命名惯例: 类名以 Mixin 结尾,表示这是用来混入的,不是独立使用的。一个类可以混入多个 Mixin,加上一个主基类——比单继承灵活得多。



5. 常见应用场景


❓ 常见问题

Q 什么时候用继承,什么时候用 Mixin?
A 继承适合"is-a"关系——狗是一种动物。Mixin 适合"has-a能力"——鸭子会游泳。能用组合(Mixin)就别用继承,因为继承层次太深后代码很难维护。一个好经验:继承不超过 3 层。
Q @property 和普通方法有什么区别?
A @property 让方法看起来像属性——访问时不用加括号,赋值时自动触发 setter。本质上是"属性拦截器"——你像操作属性一样写代码,实际上是方法在执行。
Q Python 支持多继承吗?有什么坑?
A 支持——class C(A, B)。但多继承有"钻石问题"(两个父类有同名方法到底用哪个)。Python 用 MRO(方法解析顺序)解决,从左到右搜索。Mixin 就是利用多继承的设计模式,Mixin 类很小且功能独立,避免了钻石问题。

📖 小节


📝 作业

  1. 基础题(难度⭐):定义一个 Product 类,用 @property 控制 price 属性——设置时必须为正数,获取时返回原值。

  2. 进阶题(难度⭐⭐):定义一个 AnimalMammalDog / Cat 三层继承结构。AnimalnameageMammalfur_colorDogCat 各自实现 speak() 方法。

  3. 挑战题(难度⭐⭐⭐):创建一个日志系统,包含 LoggableMixin(提供 log_info()log_error() 方法)和一个基类 Application(有 name 属性)。让 WebAppCLIApp 都继承 Application 并混入 LoggableMixin,实现日志功能。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

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

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