eval('[0 for a in A for b in B]', None, {'A':[1,2,3], 'B':[4,5,6]})
eval('[0 for a in A if B]', None, {'A':[1,2,3], 'B':True})
В обоих случаях переменная A загружается как LOAD_NAME, а B — как LOAD_GLOBAL, и, естественно, приводит к ошибке.
Вопрос: можно ли это как-то исправить?
Есть лайфхак — затащить нужные переменные внутрь цикла — тогда генератор первого уровня возьмёт A,B как LOAD_NAME, а дальше он со своими внутренними переменными управится корректно (LOAD_FAST)
eval('[0 for _A,_B in [(A,B)] for a in _A for b in _B]', None, {'A':[1,2,3], 'B':[4,5,6]})
или через лямбду (LOAD_DEREF)
eval('(lambda _A,_B: [0 for a in _A for b in _B])(A,B)', None, {'A':[1,2,3], 'B':[4,5,6]})
Но код при этом становится грязным.
Подмешивать locals в globals не очень хочется по вопросам производительности.
Перекуём баги на фичи!
Re: [python] eval не видит locals в list comprehension
Моё гугл-фу оказалось недостаточно сильно.
К>>Вопрос: можно ли это как-то исправить?
C>В python 2.7 всё (как обычно) работает (а потому что не надо было трогать то, что работает)
В 2.7 оно работало с багами (вводило имена из генератора в скоп вызывающей функции)
В 3 сделали по-другому, но тоже кривовато — сломали eval.
Перекуём баги на фичи!
Re[2]: [python] eval не видит locals в list comprehension
Здравствуйте, Буравчик, Вы писали:
Б>Что, намного становится хуже с globals?
Не я писал этот код, поэтому сказать, насколько хуже, — не знаю. Но мотивация была именно такая.
Хотя... я знаю, как пофиксить.
Напишу каскадный словарь.
Что-то в духе
class SuperGlobals:
def __init__(self, g, l):
self.g, self.l = g, l
def __getitem__(self, k):
return self.l[k] if k in self.l else self.g[k]
def __contains__(self, k):
return k in self.l or k in self.g
ну или буду профилировать и смотреть, насколько страшен чёрт, если делать
g = copy.copy(dedicated_globals())
l = dedicated_locals()
g.update(l)
eval(....., g, None)
Здравствуйте, Кодт, Вы писали:
К>Хотя... я знаю, как пофиксить. К>Напишу каскадный словарь.
Есть collestions.ChainMap, который как раз это и делает.
g = collestions.ChainMap(my_locals(), my_globals())
Хотя, для двух диктов он может работать медленнее.
К>ну или буду профилировать и смотреть, насколько страшен чёрт, если делать К>g = copy.copy(dedicated_globals())
Я бы предпочел этот вариант. Копирование словаря очень быстрое, и отсутствуют накладные расходы при выполнении кода.
Возможно, лучше использовать unpacking — это чище, и даже быстрее (!?).
g = {**my_globals(), **my_locals()}
Сделал небольшие замеры:
def copy_globals_unpack():
return {**globals(), **{'a': 1, 'b': 2, 'c': 3}}
def copy_globals_naive():
g = copy.copy(globals())
l = {'a': 1, 'b': 2, 'c': 3}
g.update(l)
return g
In [9]: %timeit SuperGlobals(globals(), {'a': 1, 'b': 2, 'c': 3})
265 ns ± 1.64 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
In [10]: %timeit copy_globals_unpack()
401 ns ± 2.05 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
In [11]: %timeit copy_globals_naive()
620 ns ± 1.09 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
Время создания объекта SuperGlobals может быть сравнимо с временем копирования словаря (конечно, зависит от количества элементов).
Ну а при работе понятно, что скопированный словарь будет сильно быстрее.
Здравствуйте, Sharov, Вы писали:
S>Дурацкий вопрос, а если существенная разница? Какая разница из какого контекста, все ведь S>в памяти, без особой косвенности.
Разница в том, что globals можно создать один раз на все вызовы eval из разных мест, а locals — для каждого конкретного места подсовывать разные.
А так придётся делать изменённые копии globals.