[python] eval не видит locals в list comprehension
От: Кодт Россия  
Дата: 05.04.23 12:32
Оценка: 6 (1)
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
От: Буравчик Россия  
Дата: 05.04.23 20:41
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Вопрос: можно ли это как-то исправить?


Передавать значения как globals? Зачем именно locals?

UPDATE:
Увидел про производительность.
Что, намного становится хуже с globals?
Best regards, Буравчик
Отредактировано 05.04.2023 20:43 Буравчик . Предыдущая версия .
Re: [python] eval не видит locals в list comprehension
От: cppguard  
Дата: 06.04.23 07:30
Оценка: 38 (1)
Здравствуйте, Кодт, Вы писали:

К>
К>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, и, естественно, приводит к ошибке.

Ну легко же гуглится https://stackoverflow.com/a/49542378/1746434, давно уже известная тема.

К>Вопрос: можно ли это как-то исправить?


В python 2.7 всё (как обычно) работает (а потому что не надо было трогать то, что работает)
Re[2]: [python] eval не видит locals в list comprehension
От: Кодт Россия  
Дата: 06.04.23 12:38
Оценка:
Здравствуйте, cppguard, Вы писали:

C>Ну легко же гуглится https://stackoverflow.com/a/49542378/1746434, давно уже известная тема.


Моё гугл-фу оказалось недостаточно сильно.

К>>Вопрос: можно ли это как-то исправить?


C>В python 2.7 всё (как обычно) работает (а потому что не надо было трогать то, что работает)


В 2.7 оно работало с багами (вводило имена из генератора в скоп вызывающей функции)
В 3 сделали по-другому, но тоже кривовато — сломали eval.
Перекуём баги на фичи!
Re[2]: [python] eval не видит locals в list comprehension
От: Кодт Россия  
Дата: 06.04.23 12:48
Оценка: 6 (1)
Здравствуйте, Буравчик, Вы писали:

Б>Что, намного становится хуже с 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)
Перекуём баги на фичи!
Отредактировано 06.04.2023 12:49 Кодт . Предыдущая версия .
Re: [python] eval не видит locals в list comprehension
От: Sharov Россия  
Дата: 06.04.23 21:42
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Подмешивать locals в globals не очень хочется по вопросам производительности.


Дурацкий вопрос, а если существенная разница? Какая разница из какого контекста, все ведь
в памяти, без особой косвенности.
Кодом людям нужно помогать!
Re[3]: [python] eval не видит locals в list comprehension
От: Буравчик Россия  
Дата: 07.04.23 05:43
Оценка: 72 (1)
Здравствуйте, Кодт, Вы писали:

К>Хотя... я знаю, как пофиксить.

К>Напишу каскадный словарь.

Есть 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 может быть сравнимо с временем копирования словаря (конечно, зависит от количества элементов).
Ну а при работе понятно, что скопированный словарь будет сильно быстрее.
Best regards, Буравчик
Отредактировано 07.04.2023 5:45 Буравчик . Предыдущая версия . Еще …
Отредактировано 07.04.2023 5:44 Буравчик . Предыдущая версия .
Re[2]: [python] eval не видит locals в list comprehension
От: Кодт Россия  
Дата: 07.04.23 12:20
Оценка: 12 (1)
Здравствуйте, Sharov, Вы писали:

S>Дурацкий вопрос, а если существенная разница? Какая разница из какого контекста, все ведь

S>в памяти, без особой косвенности.

Разница в том, что globals можно создать один раз на все вызовы eval из разных мест, а locals — для каждого конкретного места подсовывать разные.
А так придётся делать изменённые копии globals.
Перекуём баги на фичи!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.