我正在将一个库移植到Python 3。
我有一个类,其中包含许多使用range()初始化的类变量。这是一个简单的例子:
class Test(object):
base = 3
a,b,c,d = [base+y for y in range(4)]
print (Test.b)
此代码在Python 2中运行良好。
$ python2 test.py
1001
但它在Python 3中失败了:
$ python3 test.py
Traceback (most recent call last):
File "test.py", line 1, in <module>
class Test(object):
File "test.py", line 3, in Test
a,b,c,d = [base+y for y in range(4)]
File "test.py", line 3, in <listcomp>
a,b,c,d = [base+y for y in range(4)]
NameError: name 'base' is not defined
问题:发生了什么?如何在没有大量丑陋重复代码的情况下将其移植到Python 3中?
答案 0 :(得分:4)
不同之处在于,在Python3中,列表推导使用单独的变量范围而不是周围的代码,而在Python 2中,它们共享相同的变量。
在类体中添加变量的特殊内容,其中内部范围,无论它们是函数还是生成器表达式都不能“看到”非局部变量,并且您得到错误。
我认为最简单的解决方法是在一次性lambda函数中运行你的生成器,并将所需的变量作为参数传递给它。在lambda函数内部,内部作用域可以看到外部变量的通常作用域嵌套规则适用:
class Test(object):
base = 1000
a,b,c,d = (lambda base: [base+y for y in range(4)])(base)
由于Python 3允许使用自定义对象作为类主体的命名空间,因此还可以创建在全局范围内可见的命名空间。这样,类体内部范围中的代码可以使用那里的变量。
这个简短的元类和帮助器映射可以做到这一点 - 避免全局范围内的名称冲突的简单方法就是使用类名本身。这样,人们也可以获得可读性:
class AttrDict(dict):
def __getattr__(self, attr):
return self.__getitem__(attr)
def __setattr__(self, attr, value):
self.__setitem__(attr, value)
class Namespace(type):
@classmethod
def __prepare__(metacls, name, bases, **kwargs):
import sys
module_globals = sys._getframe(1).f_globals
namespace = AttrDict()
module_globals[name] = namespace
return namespace
现在,您可以像这样编写代码:
class Base(metaclass=Namespace):
x = 10
a, b, c, d = (i + Base.x for i in range(4))
del x