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. 常见应用场景
- 数据验证:用
@property.setter确保赋值时数据合法。 - 只读属性:用
@property不加 setter 创建只读属性。 - 代码复用:把通用功能放在基类,子类只写差异部分。
- 功能组合:用 Mixin 给不同的类灵活添加功能。
❓ 常见问题
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 类很小且功能独立,避免了钻石问题。📖 小节
- 封装:
_表示"别动我",__触发名称改写,@property提供优雅的访问控制 - 继承:子类自动拥有父类的属性和方法,
super()调用父类方法 - 方法重写:子类定义和父类同名的方法来覆盖
- Mixin:通过多继承给类灵活添加功能,比深层次继承更可取
- Python 没有真正的私有属性,靠约定和名称改写保护
📝 作业
-
基础题(难度⭐):定义一个
Product类,用@property控制price属性——设置时必须为正数,获取时返回原值。 -
进阶题(难度⭐⭐):定义一个
Animal→Mammal→Dog/Cat三层继承结构。Animal有name和age,Mammal加fur_color,Dog和Cat各自实现speak()方法。 -
挑战题(难度⭐⭐⭐):创建一个日志系统,包含
LoggableMixin(提供log_info()、log_error()方法)和一个基类Application(有name属性)。让WebApp和CLIApp都继承Application并混入LoggableMixin,实现日志功能。