Files

7.2 KiB
Raw Permalink Blame History

第 3-2 课Python 与 Java 的封装差异

一、本课定位

Java 通过 publicprotectedprivate 等访问修饰符实施较严格的访问控制。Python 更信任开发者主要使用命名约定和属性Property表达接口边界。

本课不重复封装的一般理论,只讲 Java 开发者迁移到 Python 时必须调整的认识。

二、本课目标

完成本课后,你将能够:

  1. 理解 Python 没有与 Java 完全等价的强制 private
  2. 解释普通名称、单下划线和双下划线的区别;
  3. 使用 @property 提供读取接口;
  4. 使用 @属性名.setter 在赋值时执行校验;
  5. 判断何时直接使用公开属性,何时需要 property
  6. 避免把 Java getter/setter 机械翻译成 Python 方法。

三、前置知识

  • Java 的访问修饰符、getter 和 setter
  • Python 类、self、实例属性和实例方法;
  • Python 异常处理。

四、差异速查表

意图 Java 常见写法 Python 常见写法
公开成员 public 普通名称,例如 name
仅供内部使用 protected 或包访问 _name 命名约定
避免子类意外覆盖 private __name 触发名称改写
读取属性 getPrice() product.price@property
校验后赋值 setPrice(value) product.price = value 配合 setter
强制安全边界 访问修饰符 下划线不能提供真正的安全隔离

五、单下划线:约定,不是限制

self._owner = owner

_owner 表示“这是内部实现,外部代码通常不要直接依赖”。但是下面的代码仍然可以运行:

print(account._owner)

因此,单下划线主要是团队沟通约定,不是运行时访问控制。

六、双下划线:名称改写,不是绝对私有

self.__balance = 100.0

Python 会进行名称改写Name Mangling把属性名称处理成类似

_Account__balance

主要目的之一是避免子类无意中使用相同名称覆盖父类属性。外部仍能通过改写后的名字访问它,所以它不是安全机制,也不能保护密码或密钥。

业务项目通常不要把所有内部属性都写成双下划线。只有确实需要避免子类名称冲突时再使用。

七、property保持属性语法同时执行方法逻辑

Java 常见写法:

public double getBalance() {
    return balance;
}

public void setBalance(double value) {
    if (value < 0) {
        throw new IllegalArgumentException();
    }
    balance = value;
}

Python 可以写成:

@property
def balance(self) -> float:
    return self.__balance

@balance.setter
def balance(self, value: float) -> None:
    if value < 0:
        raise ValueError("余额不能小于 0。")
    self.__balance = value

调用方式仍像普通属性:

print(account.balance)
account.balance = 200.0

读取时实际执行 getter 方法,赋值时实际执行 setter 方法。

八、不要为每个字段机械创建 property

如果属性只是公开保存数据,没有校验、计算或兼容需求,可以直接使用:

self.name = name

不需要照搬 Java为它创建无逻辑的 get_name()set_name()

适合使用 property 的情况包括:

  • 赋值时需要校验;
  • 返回值需要动态计算;
  • 希望提供只读接口;
  • 内部存储方式可能改变,但不希望调用方式变化。

九、只读 property

只定义 getter、不定义 setter

@property
def owner(self) -> str:
    return self._owner

调用者可以读取 account.owner,但执行 account.owner = "其他人" 时会报错。

需要注意:这仍然不是不可绕过的安全边界。调用者如果直接修改 _ownerPython 不会阻止。它表达的是正常接口,不是对恶意代码的防护。

十、初始化时复用 setter

下面的写法可以让初始值和后续赋值使用相同校验:

def __init__(self, balance: float) -> None:
    self.__balance = 0.0
    self.balance = balance

第二行 self.balance = balance 会调用 property setter。这样不会出现“对象创建时允许负数创建后却不允许负数”的规则不一致。

十一、完整示例

示例文件:

03_面向对象/3_2_Python与Java的封装差异/encapsulation_comparison.py

运行命令:

python .\03_面向对象\3_2_Python与Java的封装差异\encapsulation_comparison.py

示例输出会直接展示:

  1. 单下划线属性仍能从外部访问;
  2. property 使用属性语法执行校验逻辑;
  3. 双下划线在对象中实际发生了名称改写;
  4. 非法余额会被 setter 阻止。

十二、常见误区

12.1 认为 _name 等于 Java protected

它只是一种命名约定,不会限制外部或非子类代码访问。

12.2 认为 __name 能保护敏感信息

双下划线不是加密或权限控制。敏感信息仍需使用安全的配置、存储和权限机制。

12.3 为所有属性生成 getter 和 setter

没有额外逻辑时,直接访问公开属性更符合 Python 风格。需要保持属性语法并增加逻辑时再使用 property。

12.4 在 setter 内给 property 本身赋值

下面会无限递归:

@balance.setter
def balance(self, value: float) -> None:
    self.balance = value

setter 内应给实际存储属性赋值,例如 self.__balance = value

十三、课堂练习

打开:

03_面向对象/3_2_Python与Java的封装差异/practice.py

完成 Product 类,练习内容包括:

  1. 单下划线和双下划线属性;
  2. 只读 name property
  3. 可读写 price property
  4. 价格与折扣率校验;
  5. 使用 try...except 验证非法数据。

练习题中包含 Java 对照提醒和完整预期输出,可以直接从练习开始。

十四、参考答案

参考答案暂不写入练习文件。完成后,我会按照以下层级验证:

  • 必须修复:语法错误、运行错误、业务结果错误、关键知识点使用错误;
  • 建议改进:重复逻辑、容易产生误解的结构、明显影响可读性的写法;
  • 可选优化:非必要类型注解、输出美化和不影响理解的风格统一。

只有“必须修复”的问题会阻止课程验收。

十五、本课小结

  • Python 主要通过约定和接口设计实现封装;
  • _name 表示内部使用,但外部仍能访问;
  • __name 触发名称改写,主要用于避免子类名称冲突;
  • 双下划线不是 Java private 的完全等价物,也不是安全机制;
  • @property 让方法逻辑保留属性式调用体验;
  • setter 可以集中完成赋值校验;
  • 没有额外逻辑时,不必机械创建 getter 和 setter。

十六、验收标准

  • 能解释单下划线和双下划线的区别;
  • 能说明下划线为什么不能保护敏感信息;
  • 能定义只读 property
  • 能定义包含校验的 property setter
  • 能避免 setter 自我赋值造成无限递归;
  • 能用 property 统一初始化和后续赋值规则;
  • practice.py 能正确阻止负数价格;
  • 能说明 Python property 与 Java getter/setter 的调用差异。