Jupyter Notebook中Python类出现异常行为,是Bug还是正常特性?
你的判断完全正确,该现象是Jupyter内核的命名空间持久化特性 + Python类的定义逻辑共同导致的,具体解释如下:
1. 新Notebook直接运行报错的原因
Python定义类的执行逻辑是:先完整执行类体中所有顶层代码,最后才会把类名绑定到创建完成的类对象上。
你修改后的代码在类体中直接调用MyClass(name='id')初始化类变量Instances,此时MyClass还没有完成绑定,干净环境中没有这个名字的对象,自然会抛出NameError。
2. 旧Notebook不报错但ID异常的原因
Jupyter内核会持久保存所有运行过的代码产生的全局变量,除非你手动重启内核:
- 你第一次运行第一版
MyClass代码时,全局命名空间已经把MyClass绑定到了初始版本的类对象上,同时初始版本的Instances类变量已经因为你之前的测试操作存储了至少1个实例。 - 当你修改代码后重新运行类定义单元格时,类体中执行
Instances = [MyClass(name='id')]用到的MyClass,是全局命名空间中已经存在的旧版类对象,而非你正在定义的新版本类,所以不会报NameError。 - 此时构造占位实例调用的是旧版类的
__init__方法,计算id时取的是旧版类Instances列表的长度,所以这个占位实例的id属性被赋值为1,随后你把这个实例存入了新版类的Instances列表作为初始值。 - 后续你新建实例调用新版类的
__init__时,新版类的Instances已经有1个占位实例,所以新实例的id也被计算为1,就出现了你看到的两个实例id都是1的异常输出。
正确实现方式
如果要添加索引0的占位实例,不要在类定义体内直接构造,要等类定义完成后再操作:
class MyClass: Instances = [] def __init__(self,name=None): self.id = len(MyClass.Instances) MyClass.Instances.append(self) if name is None: self.name = 'Class %s' % self.id else: self.name = name def show(self): print('Name: %s\nId: %s' % (self.name, self.id)) @staticmethod def instance_at(i : int): if i >= len(MyClass.Instances): raise ValueError("Instance does not exist") return MyClass.Instances[i] # 类定义完成后追加占位实例 MyClass.Instances.append(MyClass(name='id'))
测试时你会看到新建的第一个用户实例id为1,name为Class 1,符合你的预期,且干净环境下直接运行也不会报错。
内容的提问来源于stack exchange,提问作者Nez
相关产品推荐
相关产品推荐

