Не можете использовать str.format (** locals ()) в понимании списка в Python 3? - PullRequest
1 голос
/ 10 марта 2019

Ниже работает в Python 2, но не 3. Есть ли способ получить доступ к локальным переменным в Python 3?Или альтернативное решение этих случаев?

[('{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, но не 3)

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

Ошибка в Python 3:

KeyError: 'local_var'

1 Ответ

2 голосов
/ 01 апреля 2019

Как пояснил @ user2357112 в комментарии, списочные представления имеют собственную локальную область действия (и, следовательно, locals() dict) в Python 3.

Сравнение:

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