首页 / Python3 入门教程 / 封装与访问控制

Python3 入门教程

封装与访问控制

本教程共 70 篇 · 第 32 篇 · 更新于 2026-07-22 · 约 5 分钟阅读

PythonPython3 入门教程封装@property访问控制名字改写

32. 封装与访问控制

本节目标:理解封装的意义,掌握单下划线 _var 和双下划线 __var 的区别,学会用 property 把方法变属性。

上一章我们给实例绑属性,外部随便读写。这在小型脚本里没问题,但项目大了,你很难保证别人不会把数据改得一塌糊涂。

封装(Encapsulation)的核心思想是:把内部数据保护起来,只暴露安全的访问方式。就像你开车,方向盘和油门是暴露的,但发动机舱盖着,你不会直接拨弄火花塞。

公开属性的问题

看这段代码:

class BankAccount:
    def __init__(self, balance):
        self.balance = balance

acc = BankAccount(1000)
acc.balance = -500  # 余额居然能变负数?
print(acc.balance)  # -500

balance 谁都能改,没有任何校验。这在真实系统里是灾难。

单下划线:约定俗成的「内部属性」

Python 没有真正的私有(private)关键字。社区约定:名字前加单下划线 _xxx 的属性,表示「内部使用,外部最好别碰」。

class Person:
    def __init__(self, name):
        self.name = name
        self._age = 0  # 约定:内部属性

p = Person('老王')
print(p._age)  # 可以访问,但不建议

单下划线只是提醒,不会阻止访问。它更像一个「此处施工」的告示牌。

双下划线:名字改写(name mangling)

如果属性名前加双下划线 __xxx,Python 会做一件特殊的事:把名字改成 _类名__xxx。这样从外部直接访问会报错。

class BankAccount:
    def __init__(self, balance):
        self.__balance = balance

    def deposit(self, amount):
        if amount <= 0:
            print("存入金额必须大于零")
            return
        self.__balance += amount

    def get_balance(self):
        return self.__balance

acc = BankAccount(1000)
# print(acc.__balance)  # AttributeError

acc.deposit(500)
print(acc.get_balance())  # 1500
Note

双下划线不是绝对安全。真有执念的人可以通过 acc._BankAccount__balance 访问到。它只是防止「无意间」的误操作,不是防黑客。

为什么需要 getter / setter

上面我们用 get_balance() 方法读取余额。这很安全,但写起来啰嗦:

print(acc.get_balance())  # 不如 acc.balance 直观

能不能既保持安全性,又像访问属性一样简洁?

@property:把方法变成属性

Python 提供了 property 装饰器(decorator),让你可以像读属性一样读方法,像写属性一样写方法:

class BankAccount:
    def __init__(self, balance):
        self.__balance = balance

    @property
    def balance(self):
        return self.__balance

    @balance.setter
    def balance(self, value):
        if value < 0:
            raise ValueError("余额不能为负数")
        self.__balance = value

acc = BankAccount(1000)
print(acc.balance)   # 像读属性一样读方法,输出 1000

acc.balance = 1500   # 像写属性一样调用 setter
print(acc.balance)   # 1500

# acc.balance = -100  # 会抛出 ValueError

拆解一下:

  • @propertybalance() 变成「只读属性」。
  • @balance.setter 给这个属性加上写入逻辑。
  • 实际数据存在 __balance 里,外部不可见。
Tip

如果你只想暴露读取,不写 @balance.setter,那这个属性就是只读的。试图赋值会报 AttributeError

只读属性的常见用法

有些属性是计算出来的,不需要存储。比如根据生日算年龄:

from datetime import datetime

class Employee:
    def __init__(self, name, birth_year):
        self.name = name
        self.__birth_year = birth_year

    @property
    def age(self):
        return datetime.now().year - self.__birth_year

e = Employee('小陈', 1995)
print(e.age)  # 输出当前年龄
# e.age = 30   # AttributeError: can't set attribute

age 没有对应的 setter,所以是只读的。这很合理:你不能直接「设置年龄」,只能改生日。

什么时候用哪种访问控制

写法含义使用场景
name公开属性简单数据,不需要校验
_name内部属性(约定)不想外部直接依赖,但继承子类可能需要
__name私有属性(名字改写)需要严格保护,防止外部和子类误改
@property属性包装读取/写入时需要额外逻辑(校验、计算、日志)
Warning

不要滥用双下划线。如果你确定子类也需要访问这个属性,用单下划线更合适。双下划线的名字改写会让继承变得麻烦。

一个完整的封装例子

把上面的知识串起来:

class Product:
    def __init__(self, name, price, stock):
        self.name = name
        self.__price = price
        self.__stock = stock

    @property
    def price(self):
        return self.__price

    @price.setter
    def price(self, value):
        if value <= 0:
            raise ValueError("价格必须大于零")
        self.__price = value

    @property
    def stock(self):
        return self.__stock

    def sell(self, quantity):
        if quantity > self.__stock:
            raise ValueError("库存不足")
        self.__stock -= quantity

p = Product('鼠标', 99, 100)
p.price = 89
p.sell(5)
print(p.stock)  # 95

name 是公开属性,简单直接。pricestockproperty 保护,修改时有校验。__price__stock 是实际存储的私有属性。

小结

  • 封装让数据和行为绑定,隐藏内部细节。
  • _var 是约定上的内部属性,仍可访问。
  • __var 触发名字改写,防止无意访问。
  • @property 让你在保持安全的同时,像操作属性一样读写数据。

封装不是「藏起来不让人看」,而是「提供安全的通道」。就像 ATM 机,你不会直接打开金库数钱,但插卡输密码就能安全取现。


来源:参考了 runoob「Python3 面向对象」、liaoxuefeng「访问限制」和「使用@property」、pythondoc「9. 类」等,改写后所得。