代码在调试控制台正常,执行/调试时触发NameError异常
问题描述
我定义了一个全局的Dict[str, str]类型字典,在类的方法中调用它时,Python触发了NameError异常。
异常存在两个特殊现象:
- 正常执行或调试模式下会触发该异常,但在调试控制台(调试暂停时)中不会出现
NameError中指向的名称并非代码里调用的变量名
代码示例
from enum import Enum from dataclasses import dataclass, field class LimitOrderStatus(Enum): NEW = '0' __status_value_to_str_map = { LimitOrderStatus.NEW.value: 'new', } @dataclass class LimitOrderState: status: LimitOrderStatus = field(init=False, default=None) # 带字符串值的Enum类型 unfilled_quantity: float = field(init=False, default=None) def __serializable_dict__(self) -> dict: str_status = __status_value_to_str_map[self.status.value] # 触发NameError的行 result = {'status': str_status, 'unfilled': self.unfilled_quantity} return result los = LimitOrderState() los.status = LimitOrderStatus.NEW print(los.__serializable_dict__())
异常信息
可以看到,异常中指向的名称是_LimitOrderState__status_value_to_str_map,但代码里调用的是__status_value_to_str_map:
File "xxx.py", line yyy, in __serializable_dict__ a = __status_value_to_str_map[self.status.value] NameError: name '_LimitOrderState__status_value_to_str_map' is not defined
已尝试的解决方法
- 删除
__pycache__文件夹中的所有文件 - 在方法开头添加
global __status_value_to_str_map语句
已找到的解决方案及疑问
将全局变量名的双下划线改为单下划线(__status_value_to_str_map -> _status_value_to_str_map)后问题解决,但不清楚原因。
问题原因解释
这是Python的**名称修饰(Name Mangling)**机制导致的:
Python会自动对类内部以双下划线开头的名称进行修饰,在名称前加上_类名前缀,以此实现类的"私有"成员保护,避免子类意外覆盖父类的属性或方法。
在你的代码中,LimitOrderState类的__serializable_dict__方法里引用__status_value_to_str_map时,Python会误以为这是类的私有成员,自动将其修饰为_LimitOrderState__status_value_to_str_map,但这个名称的变量并不存在——全局变量的名称是未被修饰的__status_value_to_str_map,因此触发NameError。
而调试控制台中不会出现异常,是因为控制台处于全局作用域,直接引用全局变量时不会触发类内部的名称修饰逻辑。
把双下划线改成单下划线后,名称不再符合双下划线开头的修饰规则,Python不会对其进行自动修饰,方法就能正确找到全局的_status_value_to_str_map变量。
内容的提问来源于stack exchange,提问作者Oliver Mohr Bonometti

