首页 新闻 会员 周边

python作用域......

0
[已解决问题] 解决于 2026-07-16 16:40
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的啊
*Tesla*的主页 *Tesla* | 老鸟四级 | 园豆:2040
提问于:2026-07-16 16:22
< >
分享
最佳答案
0

问题1:

类体中的代码不是顶层代码(不属于全局作用域)。“不符合常规”,是因为你潜意识里把 Python 的类和 Java、C++ 等静态语言里的类等同起来了。

在静态语言里,类确实只是一张“图纸”(编译期概念),编译器看一眼图纸,知道有这个结构就行了,不需要“执行”图纸。

但在 Python 中,“一切皆对象,类本身也是对象”。class 关键字本质上不是一张静态图纸,而是一条在运行时动态创建对象的“执行语句”。

  • 静态语言思维(C++/Java):类是编译期的模具。代码写在那里,编译器扫一眼,生成字节码,运行时不关类定义的事。
  • 动态语言思维(Python):类是运行期的工厂。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 怎么办?直接报错,因为类还没“激活”。
  • 所以,类体代码必须在类定义时立刻且只执行一次,这样才能正确初始化类级别的共享状态。

 

 问题2:

如果你写成正确的 y = supercls.y,它是绝对不会报错的,这也是在类体中访问父类属性的【唯一标准写法】。

下面解释为什么你尝试的其他 super 写法会报错:

1. 为什么 super.y 报错?

super 是 Python 的一个内置类/函数,它不是你的父类!你写 super.y,Python 会以为你想获取 super 这个内置类本身的 y 属性,显然它没有,所以报 AttributeError

2. 为什么 super().y 报错?(核心重点)

这是最容易错的地方!你需要区分 “类定义阶段” 和 “实例调用阶段”:

  • 类定义阶段(画图纸): 当 Python 读到 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 # 正确写法!直接通过父类名访问

*Tesla* | 老鸟四级 |园豆:2040 | 2026-07-16 16:34
  • super() 的工作原理: super() 是用于在方法内部,根据当前实例(self)的方法解析顺序(MRO)去动态寻找父类的?实例(self)的方法不对吧
  1. 实例本身没有方法: 正如你所说,方法(函数对象)是定义在类中的。实例本身只包含属性(数据字典 __dict__)。当通过实例调用方法时(如 obj.method()),Python 会通过描述符协议去 obj 的类(及其父类)中寻找该方法,并将其与实例绑定,生成一个“绑定方法(Bound Method)”。
  2. MRO 是“类”的属性,不是“实例”的属性: MRO(Method Resolution Order,方法解析顺序)存储在类的 __mro__ 属性中(例如 MyClass.__mro__)。实例没有自己的 MRO
  3. 在 Python 3 中,我们在方法里直接写无参的 super(),其实是 Python 编译器在底层做的一个“魔法”。无参的 super() 本质上等价于:

    super(__class__, self) # 在实例方法中
    # 或者
    super(__class__, cls) # 在类方法中

  1. 答案是

    为什么必须传入 self

    如果 MRO 是类的属性,super() 直接去读 __class__.__mro__ 不就行了吗?为什么非要传入 self

    为了处理“多重继承(菱形继承)”。

    假设有一个继承链:D 继承自 BC,而 BC 都继承自 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 底层执行了以下严格的步骤:


  1. 获取完整 MRO:获取 self 的实际类型(即 type(self))的 MRO 列表。
  2. 定位当前类:在这个 MRO 列表中,找到 __class__(当前方法所在的类)所处的索引位置(Index)。
  3. 寻找下一个类:从 Index + 1 的位置开始,在 MRO 列表中依次往后查找,找到第一个拥有 method 方法的类。
  4. 绑定并调用:将找到的方法绑定到 self 上并执行。
*Tesla* | 园豆:2040 (老鸟四级) | 2026-07-17 15:12
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册