【问题标题】:Executing statements in a class definition: Which variables does the interpreter know about?在类定义中执行语句:解释器知道哪些变量?
【发布时间】:2012-06-03 00:03:36
【问题描述】:

下面是我的部分类定义:

class Trial:
    font = pygame.font.Font(None, font_size)
    target_dic = {let: font.render(let, True, WHITE, BG) for let in list("ABCDEFGHJKLMNPRSTUVWX")}

部分类定义的最后一行,target_dic = {let: font.render(let, True, WHITE, BG) for let in list("ABCDEFGHJKLMNPRSTUVWX") 返回错误:未定义全局名称'font'。很公平。

但是,我尝试了以下测试用例并没有出错:

class x:
    dat = 1
    datlist = [dat for i in range(10)]

为什么第一种情况不起作用?达到字典理解时成员font不存在吗?

我是否需要将这些操作移至__init__,或者是否可以在创建类对象时只定义一次列表?

编辑:

为了清楚起见,我希望能够在创建类对象时填充列表,以减少创建 Trial 对象所花费的时间。

【问题讨论】:

  • list("ABC...") 中的list() 是多余的;字符串本身就是可迭代的,所以 for ... in "ABC.." 可以独立工作。
  • 第二个测试用例也会导致错误(self 未定义)。
  • 我认为你应该在第一种情况下使用Trial.font。我不认为你可以像那样直接引用类属性。
  • 有趣。在 dict 理解或生成器表达式中使用变量 font 时会出错。但是在列表推导中使用它没有错误。
  • Class attribute evaluation and generators 的可能副本。这个问题是关于生成器表达式而不是 dict 理解,但原因是一样的。

标签: python


【解决方案1】:

部分答案,因为它更多的是减少一些错误的路径。

如果我收回你的工作样本并输入字典理解:

class x:
    dat = 1
    datlist = {i:dat for i in range(10)}

我也明白了:

>>> NameError: global name 'dat' is not defined

所以看起来dict理解隐藏了类语句执行期间临时使用的dict,但列表理解没有。

目前在文档中找不到关于此的更多信息...

根据@interjay 评论编辑: 此post 解决了不满足范围规范的类构造。简短的故事是列表理解在 2.x 中存在错误,并且可以看到类成员,但他们不应该这样做。

【讨论】:

  • 请注意,列表推导在 Python 3 中也不起作用。
  • 好的,所以我们正在讨论 CPython 实现和实现中的副作用/错误,与规范所期望的不符......我会说列表工作的代码更多是利用的结果2.x 解释器的一个错误
  • 2.x 中的列表推导没有自己的范围(在 3.x 中它们有,并且生成器表达式以及 dict/set 推导在所有版本中都有它们)。它可能从一个错误开始,而且很讨厌,但我相信它已被记录并保存以保持兼容性(包括被其他 Python 解释器支持)。我去查一下。
  • 找到了一个关于它的脚注:docs.python.org/reference/… 所以至少它得到了承认,甚至被授予了被弃用的荣誉。
  • @delman 我知道这条评论,但在上面的例子中,它适用于ivariable,而不是dat...
【解决方案2】:

因为类装饰器是在创建类之后调用的,所以您可以使用一个来解决在类主体中引用类属性的限制,并有效地“后处理”刚刚创建的类并添加任何缺失的属性:

def trial_deco(cls):
    cls.target_dic = {let: cls.font.render(let, True, WHITE, BG) for let in "ABCDEFGHJKLMNPRSTUVWXZ"}
    return cls

@trial_deco
class Trial:
    font = pygame.font.Font(None, font_size)
    target_dic = None  # redefined by decorator

另一种方法是使属性最初成为数据描述符(也称为“属性”),它(仅)在第一次读取属性时被调用。这是通过用计算值覆盖描述符来完成的,所以它只发生一次。

class AutoAttr(object):
    """ data descriptor for just-in-time generation of instance attribute """
    def __init__(self, name, font, antialias, color, background):
        self.data = name, font, antialias, color, background

    def __get__(self, obj, cls=None):
        name, font, antialias, color, background = self.data
        setattr(obj, name, {let: font.render(let, antialias, color, background)
                                for let in "ABCDEFGHJKLMNPRSTUVWXZ"})
        return getattr(obj, name)

class Trial(object):
    font = pygame.font.Font(None, fontsize)    
    target_dic = AutoAttr('target_dic', font, TRUE, WHITE, BG)

还有其他方法可以做这样的事情,例如使用元类或定义 __new__ 方法,但值得怀疑的是任何一种方法会更好或更简单。

【讨论】:

    【解决方案3】:

    在类创建期间可用的名称是全局变量,并且名称​​已经在类的顶层定义(即,随着名称的定义,它们在类的更底层变得可用代码)。

    请注意,类级别的对象可能不是您通过实例获得的,甚至不是通过类对象获得的;最值得注意的是,在类创建期间,def 的结果仍然是一个函数而不是一个方法。

    关于 OP 的代码,此示例显示 font 已在类级别定义:

    class Trial:
        font = 'foo'
        target_dic = dict((lambda fnt:((let,fnt) for let in "ABCDEFGHJKLMNPRSTUVWX"))(font))
        target_two = []
        for let in "ABCDEFGHJKLMNPRSTUVWX":
            target_two.append(let)
    
    print(Trial.target_dic)
    print(Trial.target_two)
    

    这会产生:

    {'A': 'foo', 'C': 'foo', 'B': 'foo', 'E': 'foo', 'D': 'foo', 'G': 'foo', 'F': 'foo', 'H': 'foo', 'K': 'foo', 'J': 'foo', 'M': 'foo', 'L': 'foo', 'N': 'foo', 'P': 'foo', 'S': 'foo', 'R': 'foo', 'U': 'foo', 'T': 'foo', 'W': 'foo', 'V': 'foo', 'X': 'foo'}
    ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'U', 'V', 'W', 'X']
    

    (http://ideone.com/aC7VY)

    font 可以按预期传递给 lambda。

    问题不在于类范围内可用的名称(font 在那里可用)。问题是理解引入了自己的新范围(在 python 3 中):http://docs.python.org/py3k/reference/expressions.html#displays-for-lists-sets-and-dictionaries

    【讨论】:

    • 这并不能解释为什么在上一行中定义的dict理解中访问font会出错。
    • @Marcin,按照这个逻辑,font 属性不应该可用吗?除非我误解了你,否则这并不能解释任何事情——它重申了问题。
    • @blz 鉴于您没有提供堆栈跟踪,甚至没有提供完整和正确的代码,我对您的代码没有任何评论。
    • @Marcin:您没有意见要发表,但仍然觉得需要发布(不正确的)答案?当代码没有函数时,你期望什么堆栈跟踪?
    • 您没有引用最重要的部分:“请注意,理解是在单独的范围内执行的”。这意味着它不能从另一个范围访问变量,除非它们在闭包中可用(例如您的 lambda 提供的那个)。
    猜你喜欢
    • 1970-01-01
    • 2021-07-06
    • 2023-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-01
    • 1970-01-01
    • 2023-02-15
    相关资源
    最近更新 更多