Python属性与变量命名空间区别及面向对象常见问题解析
Python OOP作用域与类定义规则答疑
以下是问题对应测试代码:
#error statements commented out var=10 def fun(): #var=var*10 a=10 fun.b=20 fun() print(var) #print(a) print(fun.b) #print(fun.a) class clss: var=var*var c=10 #clss.d=20 #print(c) #print(clss.d) print(clss.var) print(clss.c)
问题1:为什么函数内的var无法像类定义场景中那样读取全局作用域的var值?
Python对函数和类体采用完全不同的作用域判定规则:
- 函数作用域遵循静态变量判定规则:只要函数体内任意位置存在对某个变量的赋值语句,Python就会将该变量标记为整个函数范围内的局部变量,不会向外层作用域查找。注释掉的
var = var *10语句中,因为左侧存在对var的赋值,Python会将整段函数内的var都判定为局部变量,执行到右侧取var值时,局部的var还没完成赋值,就会抛出未定义错误。 - 类体是类创建过程中临时生成的命名空间,类体代码执行时的变量查找遵循顺序查找规则:用到某个变量时,先在当前临时类命名空间里找有没有已经定义的同名变量,找不到就直接向外层(全局作用域)找,不会提前判定变量归属。类里写
var = var * var时,临时命名空间里还没有绑定var,就会自动找到全局的var=10完成计算,最终把计算结果绑定为类属性var,所以可以正常运行。
问题2:是否可以在类(示例中类名为clss)内部创建类似函数内局部变量a的局部变量?
默认不行,类体里直接赋值的变量最终都会成为类属性。
函数里的a=10是纯局部变量:它只存在于函数执行时的栈帧中,和函数对象本身没有绑定关系,函数执行结束后就会被回收,外部无法通过函数对象访问到。
类体执行阶段虽然也有临时命名空间,但类体代码执行完后,临时命名空间里所有绑定的名字都会被自动收集,绑定为类对象的属性,不会自动销毁。如果一定要定义只在类定义阶段使用、不成为类属性的临时变量,可以把临时计算逻辑包裹在类体内的立即执行函数中,函数内部的变量就是纯局部变量,执行完就会回收,不会挂到类上:
class clss: # 立即执行的匿名函数,内部变量都是局部临时变量 var = (lambda g_var: g_var * g_var)(var) c =10 # 访问不到lambda里的g_var,因为它是函数局部变量
问题3:为什么无法像在函数体内给函数对象绑定属性那样,在类体内部直接通过类名赋值定义类属性?
二者的执行时机完全不同:
- 执行
def fun():语句时,Python会先创建好函数对象,把fun这个名字绑定到对应的函数对象上,之后调用fun()执行函数体代码时,fun名字已经存在,完全可以通过fun.b=20给已经存在的函数对象绑定属性。 - 执行
class clss:语句时,流程是先创建临时类命名空间、执行类体代码,等类体代码全部执行完,才会真正创建类对象,把clss这个名字绑定到生成的类对象上。在类体里写clss.d=20的时候,类对象还没创建,clss这个名字根本没有被定义,自然无法给不存在的对象绑定属性。
问题4:为什么fun.a不可访问但clss.c可以访问?
两个变量的绑定逻辑完全不一样:
- 函数里的
a=10是函数执行时的局部变量,从头到尾都没有被绑定到fun这个函数对象上,只是存在于函数执行的临时栈帧里,函数跑完就回收了,去访问fun.a的时候,函数对象上根本没有a这个属性,自然会报错。 - 类体里的
c=10在类体执行完后,会被自动绑定到clss类对象上,成为类的固有属性,所以可以直接通过clss.c访问到对应的值。(问题描述里的f.a为笔误,实际指示例中的fun.a,“可调用”为表述偏差,实际指可通过点号访问属性)
内容的提问来源于stack exchange,提问作者harshit
相关产品推荐
相关产品推荐

