Can't use locals() in list comprehension in Python 3?

有些话、适合烂在心里 提交于 2020-03-03 17:58:54

问题


The below works in Python 2 but not 3. Is there a way to access local variables in Python 3? Or an alternative solution to these cases?

[('{name_var}_{i:02d}of{maxpg:02d}.{date_var}').format(i, **locals()) 
  for i in range(start, end)]

Error in Python 3:

KeyError: 'local_var'

Below is a simpler toy example of above (works in Python 2 but not 3)

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

Error in Python 3:

KeyError: 'local_var'


回答1:


As explained by @user2357112 in a comment, list comprehensions have their own local scope (and thus locals() dict) in Python 3.

Compare:

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

With

>>> [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}]

In the first case, the function locals is called inside the list comprehension code while in the second the result of the function call is passed as an argument to the list comprehension.

The dis module confirms that:

>>> 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

The locals function was not called. You see the call in the code of the list comprehension:

>>> 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

While

>>> 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

The locals function is called before the list comprehension execution, the iter is built and passed to the list comprehension.

Concerning your specific problem, you can force the evaluation of locals outside of the list comprehension (note the i=i: this is not a positional argument):

>>> 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']

If your version of Python is 3.6 or newer, you can use (f strings)[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']

However, I think it's not a good idea to make a lookup in locals() for every iteration. You can build your format_string once and use it in the list comprehension:

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

Or (>= 3.6):

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

Then you have:

>>> [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']


来源:https://stackoverflow.com/questions/55084171/cant-use-locals-in-list-comprehension-in-python-3

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!