类变量场景下列表推导式触发NameError的原因咨询
这个问题其实是Python类定义作用域规则的一个典型“坑”,核心原因是列表推导式在类内部有独立的作用域,没法直接访问类级别的变量,和普通for循环的执行环境完全不一样:
1. 类命名空间 vs 推导式的独立小作用域
当Python执行类定义代码时,会临时创建一个类专属的命名空间——类里的赋值、普通循环都是直接在这个空间里跑的,所以能直接拿到同空间里的其他类变量(比如版本2里的dist)。
但列表推导式(包括字典/集合推导式、生成器表达式)是个特例:在类里用它们的时候,Python会给它们单独开一个小作用域,这个小作用域的变量查找顺序是「推导式自己的变量 → 全局变量」,完全看不到外层类命名空间里的东西。
所以版本1里的代码:
distPerc = [utils.convert_to_percent(x, dist) for x in dist]
推导式里的dist会先找自己的局部变量,找不到就找全局变量,结果两边都没有,自然就抛出NameError了。
2. 普通for循环为啥能正常跑?
版本2的for循环是直接在类的命名空间里执行的,没有额外开小作用域。循环里的dist直接指向类里已经定义好的类变量,distPerc.append()也是直接操作类级别的列表,所以顺理成章就能拿到dist的值,算出正确结果。
3. 想保留列表推导式怎么改?
如果你偏爱列表推导式的写法,有两种靠谱的修复方式:
方式一:把类变量先放到全局作用域
把dist定义在类外面,让推导式能从全局作用域读到它:
import utils # 先定义成全局变量 dist = [10, 90, 0, 0, 0, 0] class A: dist = dist # 再赋值给类变量 distPerc = [utils.convert_to_percent(x, dist) for x in dist]
方式二:用类方法初始化(不用实例化)
用@classmethod来初始化类变量,既能用列表推导式,又能正常访问类变量:
import utils class A: dist = [10, 90, 0, 0, 0, 0] distPerc = [] @classmethod def init_dist_perc(cls): # 用cls.dist访问类变量 cls.distPerc = [utils.convert_to_percent(x, cls.dist) for x in cls.dist] # 初始化类变量(不用创建实例) A.init_dist_perc()
之后执行import myClasses; print(myClasses.A.distPerc)就能得到正确的[0.1, 0.9, 0.0, 0.0, 0.0, 0.0]。
不过说实话,版本2的普通for循环写法已经足够简洁直观,完全符合类定义的规范,其实是最省心的解决方案~
内容的提问来源于stack exchange,提问作者kal

