Python 3中无法在列表推导式中使用locals()吗?

7
以下代码在Python2中可以运行,但在Python3中不行。是否有一种方法可以在Python3中访问本地变量?或者这些情况的替代解决方案?
[('{name_var}_{i:02d}of{maxpg:02d}.{date_var}').format(i, **locals()) 
  for i in range(start, end)]

Python 3 错误:

KeyError: 'local_var'

以下是一个更简单的示例(在 Python 2 中工作但在 Python 3 中不工作)

local_var = 'hello'
['{local_var}'.format(**locals()) for i in range(1)]

Python 3 报错:

键错误:'local_var'


3
请给我们提供更清晰的示例,目前您的代码在任何版本中都可以运行。 - U13-Forward
你碰巧在使用一些不正常的Shell吗?***不是标准Python REPL所显示的。 - Antti Haapala -- Слава Україні
抱歉造成困惑。我已经更改了玩具示例,使其更具代表性,并重新测试以确认它在Python 2中可以工作但在Python 3中无法工作。旧的示例仅在pdb中出现问题(由于pdb特定原因)。 - user7982442
2
在Python 3中,列表推导式具有其自己的本地作用域。除非列表推导式将外部作用域的变量用作闭包变量,否则这些变量不会出现在locals()中。 - user2357112
@user2357112 对不起,我很无知,请问您说的列表推导式如何使用它们作为闭包 - 有什么例子吗? - user7982442
1
如果列表推导式包含对变量blah的直接名称查找,例如[blah for thing in stuff],那么编译器将编译代码以执行额外的工作,以支持从推导式内部访问blah。如果它只是调用locals,例如[locals()['blah'] for thing in stuff],则编译器不会激活支持查找所需的机制。 - user2357112
1个回答

6

如@user2357112在评论中解释的那样,在Python 3中,列表推导具有自己的局部作用域(因此也有locals()字典)。

比较:

>>> var=1
>>> [locals() for _ in range(1)]
[{'_': 0, '.0': <range_iterator object at 0x7f5b65cb7270>}]

使用

>>> [l for l in (locals(), )]
[{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'var': 1}]

在第一个案例中,在列表推导式代码中调用函数locals,而在第二个案例中,函数调用的结果作为参数传递给了列表推导式。 dis模块证实了这一点:
>>> from dis import dis
>>> def f(): return [locals() for _ in range(1)]
... 
>>> dis(f)
  1           0 LOAD_CONST               1 (<code object <listcomp> at 0x7fc8173bd9c0, file "<stdin>", line 1>)
              2 LOAD_CONST               2 ('f.<locals>.<listcomp>')
              4 MAKE_FUNCTION            0
              6 LOAD_GLOBAL              0 (range)
              8 LOAD_CONST               3 (1)
             10 CALL_FUNCTION            1
             12 GET_ITER
             14 CALL_FUNCTION            1
             16 RETURN_VALUE

locals 函数未被调用。您可以在列表推导式的代码中看到该调用:

>>> dis(f.__code__.co_consts[1])
  1           0 BUILD_LIST               0
              2 LOAD_FAST                0 (.0)
        >>    4 FOR_ITER                10 (to 16)
              6 STORE_FAST               1 (_)
              8 LOAD_GLOBAL              0 (locals)
             10 CALL_FUNCTION            0
             12 LIST_APPEND              2
             14 JUMP_ABSOLUTE            4
        >>   16 RETURN_VALUE

同时

>>> def g(): return [l for l in (locals(),)]
... 
>>> dis(g)
  1           0 LOAD_CONST               1 (<code object <listcomp> at 0x7f5b65cb8930, file "<stdin>", line 1>)
              2 LOAD_CONST               2 ('g.<locals>.<listcomp>')
              4 MAKE_FUNCTION            0
              6 LOAD_GLOBAL              0 (locals)
              8 CALL_FUNCTION            0
             10 BUILD_TUPLE              1
             12 GET_ITER
             14 CALL_FUNCTION            1
             16 RETURN_VALUE

locals函数在列表推导执行之前被调用,iter被构建并传递给列表推导。

关于你的具体问题,你可以强制在列表推导之外评估locals(注意i=i:这不是一个位置参数):

>>> d = locals()
>>> ['{name_var}_{i:02d}of{maxpg:02d}.{date_var}'.format(i=i, **d) for i in range(start, end)]
['VAR_00of01.2019-01-01', 'VAR_01of01.2019-01-01', 'VAR_02of01.2019-01-01', 'VAR_03of01.2019-01-01', 'VAR_04of01.2019-01-01', 'VAR_05of01.2019-01-01', 'VAR_06of01.2019-01-01', 'VAR_07of01.2019-01-01', 'VAR_08of01.2019-01-01', 'VAR_09of01.2019-01-01']

如果你的Python版本是3.6或更新版本,你可以使用(f字符串)[https://docs.python.org/3/whatsnew/3.6.html#pep-498-formatted-string-literals]
>>> [f'{name_var}_{i:02d}of{maxpg:02d}.{date_var}' for i in range(start, end)]
['VAR_00of01.2019-01-01', 'VAR_01of01.2019-01-01', 'VAR_02of01.2019-01-01', 'VAR_03of01.2019-01-01', 'VAR_04of01.2019-01-01', 'VAR_05of01.2019-01-01', 'VAR_06of01.2019-01-01', 'VAR_07of01.2019-01-01', 'VAR_08of01.2019-01-01', 'VAR_09of01.2019-01-01']

然而,我认为在每次迭代中在locals()中查找并不是一个好主意。您可以构建您的format_string一次并在列表推导式中使用它:

>>> format_string = '{name_var}_{{i:02d}}of{maxpg:02d}.{date_var}'.format(**locals()) 
>>> format_string
'VAR_{i:02d}of01.2019-01-01'

或者(>= 3.6):

>>> format_string = f'{name_var}_{{i:02d}}of{maxpg:02d}.{date_var}'

然后你有:
>>> [format_string.format(i=i) for i in range(start, end)]
['VAR_00of01.2019-01-01', 'VAR_01of01.2019-01-01', 'VAR_02of01.2019-01-01', 'VAR_03of01.2019-01-01', 'VAR_04of01.2019-01-01', 'VAR_05of01.2019-01-01', 'VAR_06of01.2019-01-01', 'VAR_07of01.2019-01-01', 'VAR_08of01.2019-01-01', 'VAR_09of01.2019-01-01']

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接