【问题标题】:Function arguments not accessible in dict-comprehension在 dict-comprehension 中无法访问函数参数
【发布时间】:2013-09-10 22:30:05
【问题描述】:

为什么在字典推导中使用eval 访问函数参数会失败?

ARGS1 = ('a1', 'b1')
def foo1(a1, b1):
    return {arg:eval(arg) for arg in ARGS1}
print foo1("A1", "B1") # NameError: name 'a1' is not defined

列表理解中的相同内容很好:

ARGS2 = ('a2', 'b2')
def foo2(a2, b2):
    return [eval(arg) for arg in ARGS2]
print foo2("A2", "B2") # OK, print: ['A2', 'B2']

没有函数也能很好地工作:

ARGS3 = ('a3', 'b3')
a3, b3 = ("A3", "B3")
print {arg:eval(arg) for arg in ARGS3} # OK, print: ['A3', 'B3']

或者如果定义了全局变量:

ARGS4 = ('a4', 'b4')
a4, b4 = ("A4", "B4")
def foo4():
    return [eval(arg) for arg in ARGS4]
print foo4() # OK, print: ['A4', 'B4']

这看起来确实像一个错误,但也许我在这里遗漏了一些东西。

(已编辑以包含无函数和有全局变量的示例)

【问题讨论】:

  • 这不是一个错误,但 Python 3 中的行为更改为更加一致。
  • 你的第一个例子是 not 一个 dict 理解,这只是一个 dict 文字。 {arg: eval(arg) for arg in ARGS} 然而是。
  • +1 @MartijnPieters 的第一条评论。另一种说法是,如果这里 存在 一个错误,那么列表理解有问题(并且在 3.0 中已修复),而 dict 理解按预期工作。跨度>
  • 附带说明,eval 妨碍了通常的名称查找规则(这意味着您实际上必须详细了解它们,并且经常解决它们,而不是只是相信它们像魔术一样工作)是您几乎不想使用eval 的众多原因之一。如果您真的想这样做,请执行loc = locals(); return {arg: loc[arg] for arg in ARGS} 之类的操作,这将使您获得适当的关闭,并且更安全,更明确和可读。

标签: python arguments closures dictionary-comprehension


【解决方案1】:

字典推导在一个新的范围内执行,就像一个函数。

因此,表达式 locals 仅限于在循环中命名的那些,在本例中为 arg考虑父函数局部变量,因为闭包在编译时绑定。 eval() 引用的名称不能使用闭包。

以下不起作用:

>>> ARGS = ('a', 'b')
>>> def bar(a, b):
...     def foo():
...         for arg in ARGS:
...             eval(arg)
...     return foo
... 
>>> print bar("A", "B")()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in foo
  File "<string>", line 1, in <module>
NameError: name 'a' is not defined

名称ab 不可用于内部foo 函数,除非编译器确定该函数确实需要访问它们:

>>> def bar2(a, b):
...     def foo():
...         a, b
...         for arg in ARGS:
...             eval(arg)
...     return foo
... 
>>> print bar2("A", "B")()
None
>>> print bar2("A", "B").func_closure
(<cell at 0x1051bac20: str object at 0x104613328>, <cell at 0x1051bacc8: str object at 0x1045971e8>)
>>> print bar2("A", "B").__code__.co_freevars
('a', 'b')

这里,a, b 行只能引用父作用域 locals(它们在 foo() 本身中没有分配),因此编译器为这些创建了闭包,并且名称已成为 locals foo().

Python 2 中的列表推导没有自己的命名空间,在 Python 3 中纠正了一个遗漏,并且没有扩展到 dict 和 set 推导。见Python list comprehension rebind names even after scope of comprehension. Is this right?

【讨论】:

  • 很好的解释。我没想到这么简洁的答案会涵盖新作用域可以采用闭包变量的事实,但如果它们仅由eval 引用,则不会,但您甚至解释了那部分。
  • 感谢您的解决方案!我批准了它,因为它非常详细,但也许你应该提到全局变量也可用:s/表达式局部变量仅限于循环中命名的那些,在这种情况下 arg/局部变量仅限于那些在循环中命名的变量循环,在本例中为 arg,加上 globals/
  • @LucasCimon:全局变量可用于模块中的所有范围。 eval() 如果您不传入显式全局映射,则自动获取当前模块全局变量。
猜你喜欢
  • 2018-04-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-25
  • 1970-01-01
  • 1970-01-01
  • 2016-05-28
  • 2019-02-01
相关资源
最近更新 更多