python---封装
文章目录
- Python 中的封装实现
- 访问修饰符
- 私有、保护、公开属性
- 1. 公开属性
- 2. 保护属性
- 3. 私有属性
- 总结与对比
- 属性装饰器
- 只读属性
- 带验证的 setter
- 延迟计算属性
- 使用 property() 函数
封装是面向对象编程的三大核心特性之一(封装、继承、多态)。
封装主要有三个目的:
1、数据隐藏:保护对象的内部状态,防止外部直接访问和修改
2、接口暴露:提供受控的访问方式,通过公共方法操作数据
3、实现隔离:将实现细节隐藏,只暴露必要的功能接口
Python 中的封装实现
访问修饰符
Python 使用命名约定来实现访问控制:
class BankAccount:def __init__(self, account_holder, balance):self.account_holder = account_holder # 公共属性self._balance = balance # 受保护的属性(约定)self.__password = "secret" # 私有属性(名称修饰)# 公共方法def get_balance(self):return self._balance# 受保护的方法def _calculate_interest(self):return self._balance * 0.05# 私有方法def __validate_password(self, input_password):return self.__password == input_password# 通过公共方法访问私有属性def withdraw(self, amount, password):if self.__validate_password(password):if amount <= self._balance:self._balance -= amountreturn Truereturn False
私有、保护、公开属性
Python 中没有真正无法访问的“私有”成员,它主要依靠一种叫做“名称修饰”的约定来实现封装,而不是严格的强制访问控制。
这三种访问级别都是通过命名约定来实现的,旨在向开发者传递明确的意图,而不是限制程序的行为。
1. 公开属性
这是默认的访问级别。任何在类中直接定义的属性(变量或方法)都是公开的。
命名方式:没有任何下划线前缀。例如:name, process_data()。
访问权限:可以从类的外部、子类以及其他任何地方自由访问和修改。
代码示例:
class MyClass:def __init__(self):self.public_attr = "I am public!" # 公开属性self.value = 10def public_method(self): # 公开方法return "Public method called"# 外部自由访问
obj = MyClass()
print(obj.public_attr) # 输出: I am public!
obj.public_attr = "Changed"
print(obj.public_attr) # 输出: Changed
print(obj.public_method()) # 输出: Public method called
2. 保护属性
这是一个弱“保护”的指示符。它告诉其他程序员:“这是一个内部属性,除非你知道自己在做什么,否则最好不要在类外部直接访问它。” 但它并不会阻止你访问。
命名方式:以单个下划线开头。例如:_internal_attr, _helper_method()。
访问权限:技术上仍然可以从外部访问,但这是一种约定俗成的信号,意味着“这是内部 API,不稳定,请勿直接使用”。Pylint 等代码检查工具会对此类外部访问发出警告。
from module import * 的影响:使用 from module import * 时,以单下划线开头的变量不会被导入。(不可把其他模块使用)
代码示例:
class MyClass2:def __init__(self):self.public = "public"self._protected_attr = "I am ‘protected‘" # 保护属性def _protected_method(self): # 保护方法return "Protected method called"def use_protected(self):# 在类的内部,可以自由使用保护属性和方法print(self._protected_attr)print(self._protected_method())# 外部仍然可以访问(但不建议这样做)
obj2 = MyClass2()
print(obj2._protected_attr) # 输出: I am ‘protected‘ (但不推荐)
obj2._protected_attr = "Changed anyway"
print(obj2._protected_attr) # 输出: Changed anyway (但不推荐)
print(obj2._protected_method()) # 输出: Protected method called (但不推荐)# 推荐的方式是通过公共接口来操作
obj2.use_protected() # 输出: Changed anyway
3. 私有属性
Python 中最强的“私有”指示符。通过一个叫做“名称修饰”的机制来实现,目的是避免在子类中意外重写私有成员。
命名方式:以双下划线开头(但不以双下划线结尾)。例如:__private_attr, __private_method()。
访问权限:Python 解释器会在运行时自动将私有属性的名称修改为 _类名__属性名 的格式。这使得在类外部通过原始名称直接访问变得困难,但如果你知道修饰后的名称,仍然可以访问。这是一种防止意外访问的机制。
class MyClass:def __init__(self):self.public = "public"self._protected = "protected"self.__private_attr = "I am private" # 私有属性def __private_method(self): # 私有方法return "Private method called"def access_private(self):# 在类的内部,仍然可以使用原始名称访问私有成员print(self.__private_attr)print(self.__private_method())obj = MyClass()# 尝试从外部直接访问私有属性(会失败)
# print(obj.__private_attr) # AttributeError: 'MyClass' object has no attribute '__private_attr'
# obj.__private_method() # AttributeError: 'MyClass' object has no attribute '__private_method'# 查看对象的所有属性,可以看到名称已被修饰
print(dir(obj))
# 输出中会包含: ... '_MyClass__private_attr', '_MyClass__private_method' ...# 通过修饰后的名称“强行”访问(虽然能做到,但强烈不建议在生产代码中这样做)
print(obj._MyClass__private_attr) # 输出: I am private
print(obj._MyClass__private_method()) # 输出: Private method called
名称修饰的核心作用:防止子类意外重写父类的私有属性。
class Parent:def __init__(self):self.__private = "Parent‘s private" # 会被修饰为 _Parent__privatedef get_private(self):return self.__private # 这里访问的是 _Parent__privateclass Child(Parent):def __init__(self):super().__init__()self.__private = "Child‘s private" # 会被修饰为 _Child__private。这是一个全新的属性,与父类的无关。child = Child()
print(child.get_private()) # 输出: "Parent‘s private"
# 因为 Parent 的 get_private 方法寻找的是 _Parent__private,
# 而 Child 实例中的 _Child__private 是完全不同的另一个属性。
总结与对比
类型 | 命名约定 | 访问权限 | 主要目的 |
---|---|---|---|
公开 | attribute | 任意位置均可访问 | 类的公共 API,安全使用。 |
保护 | _attribute | 仍可访问,但会收到警告,在其他模块不可访问 | 提示开发者“这是内部实现,请勿直接使用,因为它可能改变”。 |
私有 | __attribute | 名称被修饰,难以直接访问 | 防止子类意外重写内部属性,实现更强的封装。 |
属性装饰器
class Person:def __init__(self, name, age):self._name = nameself._age = age# Getter 方法@propertydef name(self):return self._name# Setter 方法@name.setterdef name(self, value):if isinstance(value, str) and len(value) > 0:self._name = valueelse:raise ValueError("姓名必须是非空字符串")@propertydef age(self):return self._age@age.setterdef age(self, value):if 0 <= value <= 120:self._age = valueelse:raise ValueError("年龄必须在0-120之间")# 使用示例
person = Person("张三", 25)
print(person.name) # 通过属性方式访问
person.age = 30 # 通过属性方式设置,会进行验证
只读属性
class Circle:def __init__(self, radius):self._radius = radius@propertydef radius(self):"""半径(只读)"""return self._radius@propertydef area(self):"""面积(计算属性,只读)"""return 3.14159 * self._radius ** 2circle = Circle(5)
print(f"半径: {circle.radius}") # 输出: 半径: 5
print(f"面积: {circle.area}") # 输出: 面积: 78.53975# circle.radius = 10 # 这会报错,因为 radius 是只读的
带验证的 setter
class Temperature:def __init__(self, celsius):self._celsius = celsius@propertydef celsius(self):"""摄氏温度"""return self._celsius@celsius.setterdef celsius(self, value):"""设置摄氏温度,并进行范围验证"""if value < -273.15:raise ValueError("温度不能低于绝对零度(-273.15°C)")self._celsius = value@propertydef fahrenheit(self):"""华氏温度(计算属性)"""return self._celsius * 9/5 + 32@fahrenheit.setterdef fahrenheit(self, value):"""通过华氏温度设置摄氏温度"""self.celsius = (value - 32) * 5/9temp = Temperature(25)
print(f"摄氏: {temp.celsius}°C") # 输出: 摄氏: 25°C
print(f"华氏: {temp.fahrenheit}°F") # 输出: 华氏: 77.0°Ftemp.fahrenheit = 100
print(f"摄氏: {temp.celsius}°C") # 输出: 摄氏: 37.77777777777778°C
延迟计算属性
class ExpensiveComputation:def __init__(self, data):self._data = dataself._result = Noneself._is_computed = False@propertydef result(self):"""延迟计算的昂贵操作结果"""if not self._is_computed:print("正在进行昂贵计算...")self._result = self._expensive_computation()self._is_computed = Truereturn self._resultdef _expensive_computation(self):# 模拟昂贵计算return sum(x * 2 for x in self._data)data = [1, 2, 3, 4, 5]
comp = ExpensiveComputation(data)print("第一次访问:")
print(comp.result) # 会进行计算print("第二次访问:")
print(comp.result) # 直接返回缓存结果
使用 property() 函数
除了装饰器语法,可以使用 property() 函数:
class Rectangle:def __init__(self, width, height):self._width = widthself._height = heightdef get_area(self):return self._width * self._heightdef set_width(self, value):if value <= 0:raise ValueError("宽度必须大于0")self._width = valuedef get_width(self):return self._width# 使用 property() 函数width = property(get_width, set_width)area = property(get_area)rect = Rectangle(10, 5)
print(f"宽度: {rect.width}") # 输出: 宽度: 10
print(f"面积: {rect.area}") # 输出: 面积: 50rect.width = 15
print(f"新面积: {rect.area}") # 输出: 新面积: 75