封装是面向对象编程的三大核心特性之一(封装、继承、多态)。
封装主要有三个目的:
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 -= amount
                return True
        return False

私有、保护、公开属性

Python 中没有真正无法访问的“私有”成员,它主要依靠一种叫做“名称修饰”的约定来实现封装,而不是严格的强制访问控制。
这三种访问级别都是通过命名约定来实现的,旨在向开发者传递明确的意图,而不是限制程序的行为。

1. 公开属性

这是默认的访问级别。任何在类中直接定义的属性(变量或方法)都是公开的。
命名方式:没有任何下划线前缀。例如:name, process_data()。
访问权限:可以从类的外部、子类以及其他任何地方自由访问和修改。
代码示例:

class MyClass:
    def __init__(self):
        self.public_attr = "I am public!" # 公开属性
        self.value = 10

    def 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__private

    def get_private(self):
        return self.__private # 这里访问的是 _Parent__private

class 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 = name
        self._age = age
    
    # Getter 方法
    @property
    def name(self):
        return self._name
    
    # Setter 方法
    @name.setter
    def name(self, value):
        if isinstance(value, str) and len(value) > 0:
            self._name = value
        else:
            raise ValueError("姓名必须是非空字符串")
    
    @property
    def age(self):
        return self._age
    
    @age.setter
    def age(self, value):
        if 0 <= value <= 120:
            self._age = value
        else:
            raise ValueError("年龄必须在0-120之间")

# 使用示例
person = Person("张三", 25)
print(person.name)  # 通过属性方式访问
person.age = 30     # 通过属性方式设置,会进行验证

只读属性

class Circle:
    def __init__(self, radius):
        self._radius = radius
    
    @property
    def radius(self):
        """半径(只读)"""
        return self._radius
    
    @property
    def area(self):
        """面积(计算属性,只读)"""
        return 3.14159 * self._radius ** 2

circle = 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
    
    @property
    def celsius(self):
        """摄氏温度"""
        return self._celsius
    
    @celsius.setter
    def celsius(self, value):
        """设置摄氏温度,并进行范围验证"""
        if value < -273.15:
            raise ValueError("温度不能低于绝对零度(-273.15°C)")
        self._celsius = value
    
    @property
    def fahrenheit(self):
        """华氏温度(计算属性)"""
        return self._celsius * 9/5 + 32
    
    @fahrenheit.setter
    def fahrenheit(self, value):
        """通过华氏温度设置摄氏温度"""
        self.celsius = (value - 32) * 5/9

temp = Temperature(25)
print(f"摄氏: {temp.celsius}°C")        # 输出: 摄氏: 25°C
print(f"华氏: {temp.fahrenheit}°F")     # 输出: 华氏: 77.0°F

temp.fahrenheit = 100
print(f"摄氏: {temp.celsius}°C")        # 输出: 摄氏: 37.77777777777778°C

延迟计算属性

class ExpensiveComputation:
    def __init__(self, data):
        self._data = data
        self._result = None
        self._is_computed = False
    
    @property
    def result(self):
        """延迟计算的昂贵操作结果"""
        if not self._is_computed:
            print("正在进行昂贵计算...")
            self._result = self._expensive_computation()
            self._is_computed = True
        return self._result
    
    def _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 = width
        self._height = height
    
    def get_area(self):
        return self._width * self._height
    
    def set_width(self, value):
        if value <= 0:
            raise ValueError("宽度必须大于0")
        self._width = value
    
    def 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}")     # 输出: 面积: 50

rect.width = 15
print(f"新面积: {rect.area}")   # 输出: 新面积: 75
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐