首页 > 编程开发 > Python    日期:2026-06-18 / 浏览

一、基本概念(官方定义)

在Python中,变量和方法名前的下划线有特定含义,这属于Python的命名约定(naming convention)和名称修饰(name mangling)机制:

官方定义

  • 单下划线前缀 _var:表示该标识符是"非公开API"的一部分(implementation detail)
  • 双下划线前缀 __var:触发Python的名称修饰(name mangling)机制,将其转换为_ClassName__var

这些机制在PEP 8(Python风格指南)和Python语言参考中有明确定义。

二、通俗易懂的类比

想象你在整理一间办公室:

  • 公开文件:放在桌面上,任何人都可以查阅
  • 内部文件 (_):放在标有"内部使用"的文件夹里,但没有锁
  • 机密 文件 (__):放在保险柜中并改名存档,需要特殊途径才能找到

三、实际代码演示

单下划线_:建议性的私有

class BankAccount:
    def __init__(self, account_number, balance):
        # 公开属性:账号,可以被任何地方访问
        self.account_number = account_number  
        
        # 受保护属性:余额,按约定不应直接访问,但技术上可以
        self._balance = balance               
    
    # 公开方法:显示账户信息,返回格式化的账户字符串
    def display_info(self):
        return f"Account: {self.account_number}, Balance: ${self._balance}"
    
    # 受保护方法:计算利息,内部使用,不建议外部直接调用
    def _calculate_interest(self, rate):      
        return self._balance * rate

# 创建账户实例
my_account = BankAccount("6217001234", 1000)

# 外部访问
print(my_account.account_number)         # 正常访问:6217001234
print(my_account._balance)               # 技术上可以访问,但违反约定:1000
print(my_account._calculate_interest(0.03)) # 技术上可以调用,但不符合约定:30.0

技术解析

  • 单下划线前缀在Python中没有特殊语法意义
  • 它是纯粹的命名约定,表示"这是内部实现细节"
  • Python解释器不会对其做任何特殊处理
  • dir()函数可以看到这些属性:'_balance', '_calculate_interest'

双下划线__:名称修饰机制

class UserAccount:
    def __init__(self, username, password):
        # 公开属性:用户名,可以从任何地方访问
        self.username = username          
        
        # 受保护属性:邮箱,按约定不应直接访问
        self._email = None                
        
        # 私有属性:密码,会被Python名称修饰机制改名
        self.__password = password        
    
    # 公开方法:验证密码,外部接口用于检查密码是否匹配
    def verify_password(self, input_password):
        return self.__password == input_password   # 内部可正常访问私有属性
    
    # 私有方法:重置密码,只能由类内部调用,外部无法直接访问
    def __reset_password(self):           
        self.__password = "default_password"
        return True

# 创建用户实例
user = UserAccount("zhang_san", "abc123")

# 尝试外部访问
print(user.username)     # 正常访问:zhang_san

# 这两行会引发AttributeError
try:
    print(user.__password)     # 错误:'UserAccount' object has no attribute '__password'
    user.__reset_password()    # 错误:'UserAccount' object has no attribute '__reset_password'
except AttributeError as e:
    print(f"Error: {e}")

# 但可以通过修饰后的名称访问
print(user._UserAccount__password)     # 可以访问:abc123

技术解析

  • 双下划线触发Python的名称修饰(name mangling)机制
  • 解释器将__attr自动转换为_ClassName__attr
  • 这不是为了安全,而是为了避免子类中的命名冲突
  • dir()函数会显示修饰后的名称:'_UserAccount__password', '_UserAccount__reset_password'

四、继承中的区别(关键点)

这是理解单下划线和双下划线最关键的场景:

class Parent:
    def __init__(self):
        # 受保护变量:子类可以访问和覆盖
        self._protected = "Protected variable from parent"  
        
        # 私有变量:会被名称修饰,子类无法直接访问或覆盖
        self.__private = "Private variable from parent"     
    
    # 受保护方法:子类可以访问和覆盖
    def _protected_method(self):
        return "Protected method from parent"  
    
    # 私有方法:会被名称修饰,子类无法直接访问或覆盖
    def __private_method(self):
        return "Private method from parent"    

class Child(Parent):
    def __init__(self):
        super().__init__()
        # 覆盖父类的受保护变量
        self._protected = "Protected variable overridden by child"  
        
        # 创建子类自己的私有变量,不会覆盖父类的私有变量
        self.__private = "Private variable of child (won't override parent's)"  
    
    # 覆盖父类的受保护方法
    def _protected_method(self):
        return "Protected method overridden by child"  
    
    # 创建子类自己的私有方法,不会覆盖父类的私有方法
    def __private_method(self):
        return "Private method of child (won't override parent's)"  
    
    # 公开方法:测试访问父类和自己的变量和方法
    def test_access(self):
        # 可以访问和覆盖父类的受保护成员
        print(f"Child accessing _protected: {self._protected}")
        print(f"Child calling _protected_method: {self._protected_method()}")
        
        # 无法直接访问父类的私有成员,但可以访问自己的私有成员
        try:
            print(f"Trying to access parent's __private: {self.__private}")
        except AttributeError as e:
            print(f"Child cannot directly access parent's private var: {e}")

# 测试
child = Child()
child.test_access()

# 通过名称修饰后的属性访问
print(f"Accessing parent's private var via mangled name: {child._Parent__private}")
print(f"Accessing child's private var via mangled name: {child._Child__private}")

输出结果

Child accessing _protected: Protected variable overridden by child
Child calling _protected_method: Protected method overridden by child
Child cannot directly access parent's private var: 'Child' object has no attribute '_Child__private'
Accessing parent's private var via mangled name: Private variable from parent
Accessing child's private var via mangled name: Private variable of child (won't override parent's)

技术说明

  • 单下划线(_)成员可以被子类访问和覆盖
  • 双下划线(__)成员在子类中不会被意外覆盖,因为它们在父类和子类中被分别修饰为不同的名称

五、Python不强制封装的原因

Python的设计哲学体现在"The Zen of Python"(Python之禅)中:

“明了胜于晦涩”
“实用性比纯粹性更重要”

Python创造者Guido van Rossum曾说过:

“我们都是成年人(consenting adults)”

这反映了Python的几个设计理念:

  1. 信任开发者:认为程序员有足够判断力
  2. 透明性:代码应当可理解,甚至内部实现也是
  3. 灵活性优先:有时需要访问"私有"成员(如测试、调试、高级用例)
  4. 实用主义:在某些情况下,能访问"私有"成员更为实用

六、使用指南:何时使用哪种前缀

无前缀 (公开 API)

适用场景

  • 类的公开接口
  • 希望外部代码直接使用的属性和方法
  • 稳定且有文档支持的功能
class Rectangle:
    def __init__(self, width, height):
        # 公开属性:宽度,可以从任何地方读取和修改
        self.width = width    
        
        # 公开属性:高度,可以从任何地方读取和修改
        self.height = height  
    
    # 公开方法:计算面积,稳定的对外接口
    def area(self):           
        return self.width * self.height

单下划线_(内部实现)

适用场景

  • 模块级别的"私有"函数和变量
  • 类的内部属性和方法,不希望成为公开API
  • 在子类中可能被重用的功能
  • 作为临时变量或未使用的变量
class DataProcessor:
    def __init__(self, data):
        # 公开属性:原始数据,可以从外部访问
        self.data = data          
        
        # 受保护属性:处理状态标记,内部使用,不应从外部直接访问
        self._processed = False   
    
    # 公开方法:对外接口,处理数据
    def process(self):
        # 调用内部辅助方法进行预处理
        result = self._preprocess()  
        # 进一步处理...
        self._processed = True
        return result
    
    # 受保护方法:内部辅助方法,不建议外部调用,但子类可能会使用
    def _preprocess(self):           
        # 数据预处理逻辑,将每个元素乘以2
        return [x * 2 for x in self.data]

双下划线__(名称修饰)

适用场景

  • 防止子类覆盖(名称冲突)
  • 真正的私有实现细节
  • 类的"核心"内部机制
  • 存储敏感信息(但不要仅依赖此机制做安全防护)
class AuthenticationSystem:
    def __init__(self, username, password):
        # 公开属性:用户名
        self.username = username
        
        # 私有属性:密码哈希,不希望外部直接访问,也不希望子类意外覆盖
        self.__password_hash = self.__hash_password(password)  
    
    # 公开方法:验证密码,对外接口
    def verify(self, password):
        # 内部调用私有方法计算哈希,然后比较
        hashed = self.__hash_password(password)
        return hashed == self.__password_hash
    
    # 私有方法:密码哈希算法,内部实现细节,不希望被外部访问或子类覆盖
    def __hash_password(self, password):  
        # 实际系统应使用更安全的哈希算法
        import hashlib
        return hashlib.sha256(password.encode()).hexdigest()

七、实际项目中的应用

Django框架示例

Django框架是Python中优秀的命名约定实践:

class QuerySet:
    # 公开API:过滤方法,是稳定的公开接口
    def filter(self, *args, **kwargs):
        """
        返回与给定查询参数匹配的新QuerySet
        """
        # 内部调用受保护方法实现具体功能
        return self._filter_or_exclude(False, *args, **kwargs)
    
    # 受保护方法:内部过滤实现,可能在子类中重用
    def _filter_or_exclude(self, negate, *args, **kwargs):
        # 实现细节...
        pass
    
    # 私有方法:准备查询的内部方法,完全是实现细节,不希望被外部或子类直接使用
    def __prepare_lookup(self, key):
        # 完全内部实现...
        pass

现代Python项目惯例

目前Python社区的最佳实践趋势:

  1. 谨慎使用双下划线:通常单下划线足够表达"内部使用"的意图
  2. 提供完整的API文档:清晰指明哪些是公开API,哪些是内部实现
  3. 类型注解与docstring:使用类型提示和文档字符串增强代码可读性
  4. 接口设计优于访问控制:设计良好的接口比严格的封装更重要
from typing import List, Optional

class Customer:
    """客户信息管理类"""
    
    def __init__(self, name: str, email: str) -> None:
        # 公开属性:客户姓名,可以从外部访问
        self.name = name          
        
        # 公开属性:客户邮箱,可以从外部访问
        self.email = email        
        
        # 受保护属性:购买记录列表,内部使用,不应直接从外部访问
        self._purchases: List[str] = []  
    
    def add_purchase(self, item: str) -> None:
        """
        添加购买记录
        
        Args:
            item: 购买的商品名称
        """
        # 向购买记录列表添加商品
        self._purchases.append(item)
    
    def get_purchase_history(self) -> List[str]:
        """
        获取购买历史
        
        Returns:
            购买商品列表的副本
        """
        # 返回购买记录的副本,防止外部代码意外修改内部状态
        return self._purchases.copy()  

八、小结与建议

单下划线(_)

  • Python语法上不特殊,仅为约定
  • 表示"这是内部实现,不是公开API"
  • 子类和外部技术上可以访问,但不建议直接使用

双下划线(__)

  • 触发Python的名称修饰机制
  • 主要目的是防止子类命名冲突,而非安全封装
  • 在当前类中可以正常访问,但在子类和外部访问需要使用修饰后的名称

实际使用建议

  • 默认使用无前缀(公开API)
  • 内部实现使用单下划线(_)
  • 只有在担心子类覆盖时才使用双下划线(__)
  • 记住Python的理念:“明确胜于晦涩”

无论你是初学者还是有经验的开发者,理解这些命名约定都能帮助你编写更符合Python风格的代码,并更好地理解其他Python项目。

觉得上面的内容有用吗?快来点个赞吧!

点赞() 我要打赏

温馨提示 : 本站内容来自会员投稿以及互联网,所有源码及教程均为作者总结编辑,请大家在使用过程中提前做好备份,以免发生无法预知的错误,源码类教程请勿直接用于生产环境!

 可能感兴趣的文章

1 2 3 4 5