Python局部与全局作用域检查及类内变量覆盖疑问解答
这问题问得特别到位,刚好戳中了Python类定义阶段作用域的一个容易混淆的细节——类体的作用域规则和函数内部完全不一样,咱们一步步拆解你的代码,把每个输出的原因说清楚:
首先先明确一个核心前提:Python在定义类的时候,会先创建一个临时的命名空间来执行类体里的所有代码,类内的变量赋值(比如a = "ball")都会存在这个临时空间里。但类体中的某些代码结构(比如推导式、lambda、普通函数/静态方法)的作用域,并不会自动“绑定”这个类的命名空间,而是遵循Python通用的LEGB查找规则(局部→嵌套→全局→内置),而类体本身并不属于“嵌套作用域”范畴,所以这些结构会直接跳过类的命名空间,去全局找变量。
咱们逐个看你的代码:
1. Test.b = [a for i in range(2)] → 输出 ["bat","bat"]
列表推导式在Python里是一个隐式的独立函数作用域,它不会访问类体的临时命名空间。当类定义执行到这一行时,推导式里的a会按LEGB查找:推导式自身局部没有a,没有嵌套函数,所以直接找到全局的a = "bat",最终生成的列表自然是两个"bat"。
2. Test.e = lambda: a → 输出 bat
这个lambda函数没有参数,也没有设置默认值。当你调用Test.e()时,Python会在lambda的作用域里找a:lambda自身局部没有,也没有嵌套作用域,所以直接去全局找a,返回"bat"。
3. @staticmethod def f(): return a → 输出 bat
静态方法本质就是普通函数,只是绑定在类上而已。它的作用域规则和普通函数完全一致:函数内部找a时,找不到局部的a,就直接去全局作用域查找,所以返回全局的"bat"。
接下来看后两个返回类内"ball"的情况,核心区别在于变量的求值时机:
4. Test.g = lambda a=a: a → 输出 ball
这里的lambda用了默认参数,而Python中默认参数的值是在函数(lambda)定义的时候计算的。当类定义执行到这一行时,类体的临时命名空间里已经存在a = "ball"(前面已经执行了a = "ball"),所以默认参数a会捕获这个类内的a的值。当你调用Test.g()时,没有传入参数,就会使用这个预先计算好的默认值"ball"。
5. Test.d = a → 输出 ball
这是类体里最直接的变量赋值,直接引用类体临时命名空间里的a。此时类体已经执行过a = "ball",所以d直接被赋值为"ball"。
总结一下关键规则
- 类体在定义时是一个临时执行环境,但类内的推导式、lambda(无默认参数)、函数/静态方法不会自动访问这个环境的变量,而是遵循普通LEGB规则,优先找全局变量。
- 函数/lambda的默认参数值是在定义阶段计算的,会捕获定义时所在环境(这里就是类体)的变量。
- 类体中直接的变量赋值,会直接使用当前类体命名空间里的变量。
内容的提问来源于stack exchange,提问作者swami

