x = 11 def f(): print("func f") def g(): x = 12 print("g的x") class supercls(): print(111) y = 3 def supercls_m(self): y = 1 self.y = 12 class cls(supercls): y = supsercls.y def cls_n(self): self.y = 66 print(y) 现在问的作用域: 问题1:上面的代码我脑海里记得有个特殊情况就是类里面第一层缩进的也是顶层代码,级别和最外层一样,所以该脚本运行时print(111) 会执行,但是下面的y=3也应该会被最后一行print(y)执行吧,报错了y未被定义 问题2: y = supsercls.y 我写成super.y或者super().y或者super(supercls).y都报错,但是我的supercls类里面是有y = 3的啊
问题1:
类体中的代码不是顶层代码(不属于全局作用域)。“不符合常规”,是因为你潜意识里把 Python 的类和 Java、C++ 等静态语言里的类等同起来了。
在静态语言里,类确实只是一张“图纸”(编译期概念),编译器看一眼图纸,知道有这个结构就行了,不需要“执行”图纸。
但在 Python 中,“一切皆对象,类本身也是对象”。class 关键字本质上不是一张静态图纸,而是一条在运行时动态创建对象的“执行语句”。
class 语句本身就是一条可执行代码,它运行的结果,就是在内存中动态制造出了一个“类对象”。1. 底层揭秘:Python 创建类时到底在干嘛?
当 Python 解释器遇到 class MyClass: 时,它并不是简单地“记住”这个结构,它在底层其实偷偷做了这三步(伪代码):
# 步骤 1:准备一个空的字典(命名空间),用来装类里面的东西 class_dict = {} # 步骤 2:【立即执行】类体代码,并把产生的变量和方法塞进这个字典里 exec(""" y = 3 def my_method(self): pass """, globals(), class_dict) # 步骤 3:调用 type() 函数,用这个字典“捏”出一个真正的类对象 MyClass = type('MyClass', (object,), class_dict)
看明白了吗? Python 为了知道你的类里面到底有哪些属性(y)和方法(my_method),它必须把类体里的代码跑一遍,把跑出来的结果收集到class_dict里。如果不执行,它怎么知道类里有什么?
class MyClass: print("初始化类属性...") count = 0 # 类属性 def __init__(self): MyClass.count += 1
obj = MyClass(),它都会重新跑一遍类体,重新打印“初始化...”,并且把 count 重新赋值为 0。这会导致类属性永远无法在多个实例间共享,每次实例化都会把类属性重置,逻辑完全崩溃。MyClass.count 怎么办?直接报错,因为类还没“激活”。
如果你写成正确的 y = supercls.y,它是绝对不会报错的,这也是在类体中访问父类属性的【唯一标准写法】。
下面解释为什么你尝试的其他 super 写法会报错:
1. 为什么 super.y 报错?
super 是 Python 的一个内置类/函数,它不是你的父类!你写 super.y,Python 会以为你想获取 super 这个内置类本身的 y 属性,显然它没有,所以报 AttributeError。
2. 为什么 super().y 报错?(核心重点)
这是最容易错的地方!你需要区分 “类定义阶段” 和 “实例调用阶段”:
class cls(supercls): 并执行里面的代码时,cls 这个类还没有完全创建完毕,更没有实例化出任何对象(没有 self)。super() 的工作原理: super() 是用于在方法内部,根据当前实例(self)的方法解析顺序(MRO)去动态寻找父类的。self)都不存在,super() 根本不知道该代理谁,所以直接报错(通常报 RuntimeError: super(): no arguments)。super() 只能在类的实例方法内部使用。3. 为什么 super(supercls).y 报错?
super 的完整语法是 super(当前类, 当前实例或当前类)。在类体中,当前类 cls 还没创建完,你传参是不完整的,而且同样受限于“类未创建完毕”的规则,所以也会报错。
总结问题 2: 在类体内部(非方法内部),老老实实用父类的类名去访问父类的类属性:
class cls(supercls):
y = supercls.y # 正确写法!直接通过父类名访问
super() 的工作原理: super() 是用于在方法内部,根据当前实例(self)的方法解析顺序(MRO)去动态寻找父类的?实例(self)的方法不对吧__dict__)。当通过实例调用方法时(如 obj.method()),Python 会通过描述符协议去 obj 的类(及其父类)中寻找该方法,并将其与实例绑定,生成一个“绑定方法(Bound Method)”。__mro__ 属性中(例如 MyClass.__mro__)。实例没有自己的 MROsuper(),其实是 Python 编译器在底层做的一个“魔法”。无参的 super() 本质上等价于:
super(__class__, self) # 在实例方法中
# 或者
super(__class__, cls) # 在类方法中
答案是
为什么必须传入 self?
如果 MRO 是类的属性,super() 直接去读 __class__.__mro__ 不就行了吗?为什么非要传入 self?
为了处理“多重继承(菱形继承)”。
假设有一个继承链:D 继承自 B 和 C,而 B 和 C 都继承自 A。
B 类自己的 MRO 是:[B, A, object]D 的实例调用 B 中的方法时,运行时的真实 MRO 应该是 D 的 MRO:[D, B, C, A, object]。如果 super() 只认当前类 B 的 MRO,它在 B 中调用 super() 时就会直接去找 A,从而跳过了 C,这就破坏了多重继承的协同工作。
因此,super() 必须接收 self,通过 type(self) 获取最底层实际实例化那个类(即 D)的完整 MRO 链条,才能保证调用顺序的绝对正确。
class A: def process(self): print("A process") class B(A): def process(self): print("B process start") super().process() # 等价于 super(B, self).process() print("B process end") class C(A): def process(self): print("C process start") super().process() # 等价于 super(C, self).process() print("C process end") class D(B, C): def process(self): print("D process start") super().process() # 等价于 super(D, self).process() print("D process end") # 查看 D 的 MRO print("D 的 MRO:", [cls.__name__ for cls in D.__mro__]) # 输出: D 的 MRO: ['D', 'B', 'C', 'A', 'object'] print("-" * 20) d = D() d.process() D 的 MRO: ['D', 'B', 'C', 'A', 'object'] -------------------- D process start B process start C process start A process C process end B process end D process end 原理解析(以 B 中的 super() 为例): 当执行到 B 中的 super().process() 时,底层是 super(B, self).process()。 super 获取 type(self)(即 D)的 MRO:[D, B, C, A, object]。 super 在 MRO 中找到当前类 B 的位置,索引是 1。 super 从索引 1 + 1 = 2 的位置开始找 process 方法。索引 2 是 C 类。 因此,B 中的 super() 调用的根本不是它的父类 A,而是 MRO 链条中的下一个类 C!
super() 的真正工作原理(查找算法)
当你调用 super(__class__, self).method() 时,Python 底层执行了以下严格的步骤:
self 的实际类型(即 type(self))的 MRO 列表。__class__(当前方法所在的类)所处的索引位置(Index)。Index + 1 的位置开始,在 MRO 列表中依次往后查找,找到第一个拥有 method 方法的类。self 上并执行。