为什么我的生成器表达式会在类属性上引发NameError?

时间:2016-07-06 15:50:44

标签: python python-3.x

class test(object):
    data = 1
    dataSet = tuple(data for i in range(2))

这段代码是获取NameError <genexpr>的最简单方法。

感谢Jim,我知道解决它的最简单方法是简单地使用lambda:

class test(object):
    data = 1
    dataSet = (lambda d = data: tuple(d for i in range(2)))()

我知道我也可以装饰类来修改完全创建类对象时的元组。

def tuple_data(cls):
    setattr(cls, 'dataSet', tuple(getattr(cls, 'dataSet')))
    return cls

现在:

@tuple_data
class test(object):
    data =  1
    dataSet = (test.data for i in range(2))

我主要是试图理解幕后的计算。

根据Jim的回答,<genexpr>无法访问班级数据,因为在评估<genexpr>时它并不存在。 截至this answer<genexpr>无法访问该类&#39;命名空间,仅限于其自己的本地范围。问题是<genexpr>使用LOAD_GLOBAL这是相当混乱的。

最重要的是,Jim的解决方案正在发挥作用,但我无法理解究竟是怎么回事。 我想装饰器解决方案的工作原理很简单,因为装饰器在完全创建或调用实例化后修改类功能。 另一方面,我无法理解为什么lambda正在工作。

1 个答案:

答案 0 :(得分:2)

你可以通过不使用理解而不是将单个元素列表乘以初始化元组所需的量来实现这一点:

data = tuple([dataSet] * 2)

或者,为了突出显示丑陋但有趣lambdas的可能性,将其作为参数传递给lambda函数并调用它:

data = (lambda dataSet=dataSet: tuple(dataSet for i in range(2)))()

应该这样做。

至于这背后的原因,我似乎无法找到任何硬文档,PEP 289只是声明鼓励用户只在函数内部创建生成器表达式,并在更复杂的场景中回退到函数生成器。它似乎只是那种方式。

显然是这样编译的。我继续检查code对象,找到一个使用生成器表达式来查看发生了什么的类,首先定义一个类:

cls = """
class test(object):
    i = 10
    d = tuple(i for j in range(2))
"""

然后,编译它:

cls_code = compile(cls, "stdin", mode = 'exec')

现在,我们对它进行反汇编并检查位于cls_code.co_consts[0]的类定义的内容:

import dis
dis.dis(cls_code.co_consts[0])
  2           0 LOAD_NAME                0 (__name__)
              3 STORE_NAME               1 (__module__)
              6 LOAD_CONST               0 ('test')
              9 STORE_NAME               2 (__qualname__)

  3          12 LOAD_CONST               1 (10)
             15 STORE_NAME               3 (i)

  4          18 LOAD_NAME                4 (tuple)
             21 LOAD_CONST               2 (<code object <genexpr> at 0x7ff3e08584b0, file "stdin", line 4>)
             24 LOAD_CONST               3 ('test.<genexpr>')
             27 MAKE_FUNCTION            0
             30 LOAD_NAME                5 (range)
             33 LOAD_CONST               4 (2)
             36 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             39 GET_ITER
             40 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             43 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             46 STORE_NAME               6 (d)
             49 LOAD_CONST               5 (None)
             52 RETURN_VALUE

我们在类中看到,加载和存储是通过LOAD_NAME操作码(使用包含代码对象内部名称的co_names元组)完成的。

另一方面,生成器表达式以不同方式加载名称,生成器表达式的代码对象位于cls_code.co_consts[0].co_consts[2]中(它是类定义的code对象中的常量,如果我们将其反汇编,我们可以看到它在尝试查找LOAD_GLOBAL时使用i

dis.dis(cls_code.co_consts[0].co_consts[2])
  4           0 LOAD_FAST                0 (.0)
        >>    3 FOR_ITER                11 (to 17)
              6 STORE_FAST               1 (j)
              9 LOAD_GLOBAL              0 (i)
             12 YIELD_VALUE
             13 POP_TOP
             14 JUMP_ABSOLUTE            3
        >>   17 LOAD_CONST               0 (None)
             20 RETURN_VALUE

另一种解决方法是注意到你实际上已经执行了#39;生成器表达式,因为您将它包装在tuple调用中,因为它在创建类之前执行,因此class_name.attr形式的引用将失败。

如果您在没有tuple电话的情况下定义了您的课程,您可以按照以下方式开始工作:

class test(object):
    dataSet = damage("ranged", "energy", 1, 0, 0.5, 0, 0, -0.5, 0,    0, False)
    data = (test.dataSet for i in range(2)

然后,在创建完课程后,您现在可以将其打包到tuple来电,并且test.dataSet的查找将会成功:

test.data = tuple(test.data)

如果这是您不想手动执行的操作,您可以随时创建一个装饰器来自动执行此操作:

def tuple_data(cls):
    setattr(cls, 'data', tuple(getattr(cls, 'data')))
    return cls

现在:

@tuple_data
class test(object):
    dataSet = damage("ranged", "energy", 1, 0, 0.5, 0, 0, -0.5, 0,    0, False)
    data = (test.dataSet for i in range(2)

在创建课程data后,会将tuple传递给test