类属性依赖其他类属性:生成器表达式报错原因及替代方案咨询
类属性依赖中的生成器表达式为何报错?
问题场景
想创建依赖其他类属性的类属性时,字符串操作的写法能正常运行:
class A: foo = "foo" bar = foo[::-1] print(A.bar) # 输出 'oof'
但换成生成器表达式处理元组时,却直接抛出NameError:
x = tuple('nice cup of tea') class A: remove = ('c','o','a',' ') remains = tuple(c for c in x if not c in remove) print(A.remains)
报错信息:
Traceback (most recent call last): File "[***.py]", line 3, in <module> class A: File "[***.py]", line 5, in A remains = tuple(c for c in x if not c in remove) File "[***.py]", line 5, in <genexpr> remains = tuple(c for c in x if not c in remove) NameError: name 'remove' is not defined
你找到一个临时解决方案,但不满意:
x = tuple('nice cup of tea') def sub(a,b): return tuple(c for c in a if not c in b) class A: remove = ('c','o','a',' ') remains = sub(x, remove) print(A.remains)
不满意的点:
- 搞不懂为什么加个中间函数就能解决
- 不想为这么简单的操作额外定义函数
核心原因:作用域规则差异
Python定义类时,会临时创建一个类命名空间来处理属性赋值,但生成器表达式(以及Python3的列表推导式)会创建独立的局部作用域,这个作用域无法直接访问类定义时的临时命名空间:
- 字符串操作
foo[::-1]是直接在类的临时命名空间内求值的,能直接找到foo这个类属性; - 生成器表达式
(c for c in x if not c in remove)属于独立作用域,它只会去全局作用域和自己的局部作用域找变量,根本看不到类临时命名空间里的remove,所以报错。
为什么中间函数可行?
调用sub(x, remove)时,remove是先在类命名空间里被解析为具体的元组值,再作为参数传递给函数的。函数内部的生成器表达式访问的是函数局部变量b,而不是类属性,自然不存在作用域问题。
更简洁的替代方案
方案1:类定义完成后补全属性
直接在类定义结束后,利用已存在的类属性赋值,完全避开作用域问题:
x = tuple('nice cup of tea') class A: remove = ('c','o','a',' ') # 类定义完成后,A.remove已存在于全局作用域 A.remains = tuple(c for c in x if c not in A.remove) print(A.remains)
方案2:用filter+lambda替代生成器表达式
lambda是在类的临时命名空间内创建的,能直接访问同一空间下的remove属性,写法同样简洁:
x = tuple('nice cup of tea') class A: remove = ('c','o','a',' ') remains = tuple(filter(lambda c: c not in remove, x)) print(A.remains)
内容的提问来源于stack exchange,提问作者lg53
相关产品推荐
相关产品推荐

