查看: 77|回复: 1

Python命名约定:单下划线_与双下划线__的封装机制及继承陷阱

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
在Python开发中,下划线前缀不是装饰,而是有明确语义的命名约定。很多新手以为 __var 是“私有变量”,实际上它触发的是名称修饰(name mangling)。本文通过代码实例和继承场景,带你彻底搞懂单下划线_和双下划线__的区别,并给出项目中的使用建议。

一、概念与类比

Python官方定义(PEP 8):
- 单下划线前缀 _var:表示该标识符是“非公开API”或实现细节,仅是一个约定。
- 双下划线前缀 __var:触发名称修饰,Python解释器将其转换为 _ClassName__var。

类比:公开文件放桌面;内部文件放标有“内部使用”的文件夹但没有锁;机密文件放保险柜并改名,需要特殊途径才能找到。

二、代码演示

2.1 单下划线 _ :建议性的私有
  1. class BankAccount:
  2.     def __init__(self, account_number, balance):
  3.         self.account_number = account_number   # 公开
  4.         self._balance = balance                 # 受保护,约定不应直接访问
  5.     def display_info(self):
  6.         return f"Account: {self.account_number}, Balance: ${self._balance}"
  7.     def _calculate_interest(self, rate):
  8.         return self._balance * rate
  9. my_account = BankAccount("6217001234", 1000)
  10. print(my_account.account_number)      # 6217001234
  11. print(my_account._balance)            # 1000,技术上可访问,但违反约定
  12. print(my_account._calculate_interest(0.03))  # 30.0
复制代码

技术要点:
- 单下划线没有特殊语法意义,纯粹是命名约定。
- dir() 可以看到这些属性:'_balance', '_calculate_interest'。

2.2 双下划线 __ :名称修饰机制
  1. class UserAccount:
  2.     def __init__(self, username, password):
  3.         self.username = username
  4.         self._email = None
  5.         self.__password = password          # 触发名称修饰
  6.     def verify_password(self, input_password):
  7.         return self.__password == input_password
  8.     def __reset_password(self):
  9.         self.__password = "default_password"
  10.         return True
  11. user = UserAccount("zhang_san", "abc123")
  12. print(user.username)                     # zhang_san
  13. try:
  14.     print(user.__password)               # AttributeError
  15. except AttributeError as e:
  16.     print(f"Error: {e}")
  17. try:
  18.     user.__reset_password()              # AttributeError
  19. except AttributeError as e:
  20.     print(f"Error: {e}")
  21. print(user._UserAccount__password)       # abc123,绕过名称修饰
复制代码

技术要点:
- 双下划线触发名称修饰,将 __attr 转换为 _ClassName__attr。
- 目的不是安全,而是避免子类中的命名冲突。
- dir() 会显示 '_UserAccount__password', '_UserAccount__reset_password'。

三、继承中的区别(关键点)
  1. class Parent:
  2.     def __init__(self):
  3.         self._protected = "Protected variable from parent"
  4.         self.__private = "Private variable from parent"
  5.     def _protected_method(self):
  6.         return "Protected method from parent"
  7.     def __private_method(self):
  8.         return "Private method from parent"
  9. class Child(Parent):
  10.     def __init__(self):
  11.         super().__init__()
  12.         self._protected = "Protected variable overridden by child"
  13.         self.__private = "Private variable of child (won't override parent's)"
  14.     def _protected_method(self):
  15.         return "Protected method overridden by child"
  16.     def __private_method(self):
  17.         return "Private method of child (won't override parent's)"
  18.     def test_access(self):
  19.         print(f"Child accessing _protected: {self._protected}")
  20.         print(f"Child calling _protected_method: {self._protected_method()}")
  21.         try:
  22.             print(f"Trying to access parent's __private: {self.__private}")
  23.         except AttributeError as e:
  24.             print(f"Child cannot directly access parent's private var: {e}")
  25. child = Child()
  26. child.test_access()
  27. # 输出:
  28. # Child accessing _protected: Protected variable overridden by child
  29. # Child calling _protected_method: Protected method overridden by child
  30. # Child cannot directly access parent's private var: 'Child' object has no attribute '_Child__private'
  31. print(f"Accessing parent's private var via mangled name: {child._Parent__private}")
  32. print(f"Accessing child's private var via mangled name: {child._Child__private}")
复制代码

结论:单下划线成员可以被子类访问和覆盖;双下划线成员在父类和子类中分别被修饰为不同名称,不会意外覆盖。

四、使用指南

- 无前缀:公开API,稳定且文档支持的接口。
  1. class Rectangle:
  2.     def __init__(self, width, height):
  3.         self.width = width
  4.         self.height = height
  5.     def area(self):
  6.         return self.width * self.height
复制代码

- 单下划线 _ :内部实现,子类可重用,但不建议外部直接使用。
  1. class DataProcessor:
  2.     def __init__(self, data):
  3.         self.data = data
  4.         self._processed = False
  5.     def process(self):
  6.         result = self._preprocess()
  7.         self._processed = True
  8.         return result
  9.     def _preprocess(self):
  10.         return [x * 2 for x in self.data]
复制代码

- 双下划线 __ :防止子类覆盖,真正的私有细节(但不要依赖做安全防护)。
  1. class AuthenticationSystem:
  2.     def __init__(self, username, password):
  3.         self.username = username
  4.         self.__password_hash = self.__hash_password(password)
  5.     def verify(self, password):
  6.         hashed = self.__hash_password(password)
  7.         return hashed == self.__password_hash
  8.     def __hash_password(self, password):
  9.         import hashlib
  10.         return hashlib.sha256(password.encode()).hexdigest()
复制代码

五、实际项目中的惯例

Django 框架的 QuerySet 类使用单下划线作为内部过滤实现,双下划线用于完全内部的查询准备方法,外部只暴露 filter() 等公开API。现代Python项目建议:
- 谨慎使用双下划线,单下划线通常足够。
- 提供完整的API文档。
- 使用类型注解与docstring。
- 好的接口设计比严格的访问控制更重要。
  1. from typing import List, Optional
  2. class Customer:
  3.     """客户信息管理类"""
  4.     def __init__(self, name: str, email: str) -> None:
  5.         self.name = name
  6.         self.email = email
  7.         self._purchases: List[str] = []
  8.     def add_purchase(self, item: str) -> None:
  9.         self._purchases.append(item)
  10.     def get_purchase_history(self) -> List[str]:
  11.         return self._purchases.copy()
复制代码

六、小结

- 单下划线_ :纯约定,子类可覆盖,表示“内部实现”。
- 双下划线__ :触发名称修饰,防止子类意外覆盖,但在当前类和子类中可以通过 _ClassName__attr 访问。
- 默认优先使用无前缀(公开API),内部实现用单下划线,只有在担心子类覆盖时才用双下划线。

理解这些命名约定,能让你写出更符合Python风格的代码,也更易读懂大型Python项目。
回复

使用道具 举报

发表于 半小时前 | 显示全部楼层

Re: Python命名约定:单下划线_与双下划线__的封装机制及继承陷阱

楼主总结得非常好,特别是继承中双下划线名称修饰导致子类无法覆盖父类同名属性的这个点,很多新手都会在这上面踩坑。实际项目里,我觉得单下划线加文档注释已经足够表达“内部实现,请勿直接使用”的意图,双下划线更适合用来防止子类意外重写基类的关键内部逻辑(比如一些框架的钩子方法)。另外补充一个小细节:Python 中还有前后各双下划线的 `__var__`(魔法方法),这种不会触发名称修饰,和楼主说的双下划线前缀是两回事。感谢分享,很实用的指南!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-6-18 11:57 , Processed in 0.026563 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部