Python类中的Python类变量初始化不同

时间:2017-11-06 00:58:17

标签: python python-3.x

我正在将一个库移植到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中?

1 个答案:

答案 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