【问题标题】:Python class variable assignment using dictionary comprehension使用字典理解的 Python 类变量赋值
【发布时间】:2019-04-15 16:12:49
【问题描述】:

在类定义期间,定义为字典的类变量用于构造第二个字典类变量,这是从第一个字典类变量缩减的子集,如下所示:

class C(object):
    ALL_ITEMS = dict(a='A', b='B', c='C', d='D', e='E')
    SUBSET_X = {k: v for k, v in ALL_ITEMS.items() if k in ('a', 'b', 'd')}  # (this works)
    SUBSET_Y = {k: ALL_ITEMS[k] for k in ('a', 'b', 'd')}  # (this fails)

相当简单的东西,但执行此代码的最终效果让我感到非常惊讶。我的第一种方法是第 4 行的代码,但我不得不求助于第 3 行的解决方案。我显然无法掌握字典理解范围规则的一些微妙之处。

具体来说,在失败的情况下引发的错误是:

File "goofy.py", line 4, in <dictcomp>
   SUBSET_Y = {k: ALL_ITEMS.get(k) for k in ('a', 'b', 'd')}
NameError: name 'ALL_ITEMS' is not defined

由于几个不同的原因,这个错误的性质让我感到困惑:

  1. SUBSET_Y 的赋值是一个格式良好的字典理解,并引用了一个应该在范围内且可访问的符号。
  2. 在随后的情况下(分配给SUBSET_X),这也是一个字典理解,符号ALL_ITEMS 是完美定义和可访问的。因此,在失败的情况下引发的异常是 NameError 的事实似乎显然是错误的。 (或者充其量是误导。)
  3. 为什么items()__getitem__get() 的范围规则不同? (在失败的情况下,将ALL_ITEMS[k] 替换为ALL_ITEMS.get(k) 也会出现同样的异常。)

(即使作为 Python 开发人员十多年,我也从未遇到过这种失败,这要么意味着我很幸运,要么过着隐蔽的生活:^)

在各种 3.6.x CPython 版本以及 2.7.x 版本中都会出现同样的故障。

编辑:不,这不是上一个问题的重复。这与列表推导有关,即使要对字典推导进行相同的解释,也不能解释我引用的两种情况之间的区别。而且,这不仅仅是 Python 3 独有的现象。

【问题讨论】:

  • 我在您的代码中的任何地方都看不到 ALL_KEYS。错误是否引用了 ALL_ITEMS?

标签: python class dictionary scoping


【解决方案1】:

有一个小细节可以解释为什么第一个版本有效但第二个版本失败。第二个版本失败的原因与this question 中给出的原因相同,即所有理解构造(在 Python 3 中,在 Python 2 中,list-comprehensions 的实现方式不同)创建了一个函数范围,其中所有本地名称 -发生绑定。但是,在类范围内定义的函数无法访问类范围内的名称。这就是为什么您必须使用self.MY_CLASS_VARMyClass.MY_CLASS_VAR 从方法访问类变量的原因。

您的第一个案例确实有效的原因很微妙。根据language reference

理解由一个表达式组成,后跟至少 一个 for 子句和零个或多个 for or if 子句。在这种情况下, 新容器的元素是那些将由 将每个 for 或 if 子句视为一个块,从左侧嵌套 向右,并评估表达式以产生每个元素 到达最里面的块的时间。

但是,除了最左边的可迭代表达式 for 子句,理解在单独的隐式嵌套中执行 范围。 这确保分配给目标列表中的名称不会 “泄漏”到封闭范围内。

最左边的 for 子句中的可迭代表达式被求值 直接在封闭范围内,然后作为参数传递给 隐式嵌套范围。

所以,在第一种情况下,ALL_ITEMS.items() 在最左边的 for 子句中,所以它直接在封闭范围内计算,在这种情况下,是类范围,所以它很高兴找到ALL_ITEMS 名称。

【讨论】:

  • 所以有几件事,1) 当然,一个完全限定的名称(例如,MyClass.MY_CLASS_VAR)在分配类变量的表达式中是不可访问的在类定义中,我怀疑你打算建议。此外,2) 与 Python 3 相同的解决方法同样适用于 Python 2,因此这似乎不像之前提到的那样是 Python 3-ism。最后,3) 是的,你的解释与规范相比是成立的,但是伙计,对于毫无戒心的人来说,这是否曾经是一个微妙的绊脚石!
  • @trenchant 1) 是的,MyClass 在类定义期间将不可用,因为它不存在,并且类范围仅在定义期间存在。 2) 对于列表理解,这仅适用于 Python 3,因为在 Python 2 中,它们的实现没有创建类范围,而是具有“泄漏”范围。 dict 和 set 理解从未受此影响,在 Python 3 中,列表理解被重新实现为像 set 和 dict 理解结构一样工作 3) 是的,它很微妙。我只是避免在类范围内使用理解结构。
猜你喜欢
  • 1970-01-01
  • 2020-07-28
  • 2013-07-24
  • 2014-05-06
  • 2013-08-31
  • 1970-01-01
  • 2012-08-01
  • 1970-01-01
  • 2016-01-24
相关资源
最近更新 更多