LINUX.ORG.RU

Не понимаю eval, exec в питоне

 , ,


3

2

Как запустить код с ветвлением и нормально получить результат в переменную? Ну вообще что-то не получается...
Кто подскажет хорошим примером? Код, который нужно запустить через eval|exec примерно такой:

if VALUE <= 10:
    ВОЗВРАТИТЬ_ФЛОАТ
else:
    ВОЗВРАТИТЬ_ДРУГОЙ_ФЛОАТ
VALUE - здесь будет локальной переменной

★★★★

Последнее исправление: FIL (всего исправлений: 3)

Напиши поширше, что тебе нужно? eval везде считается дурным тоном, наверняка другой путь есть.

anonymous
()
Ответ на: комментарий от anonymous

Ну тут очень большое ветвление на строк 30 и константы в нем могут менятся. В основном коде(в репозитории) это хранить не вариант. Поэтому на ум приходит только eval.
Описать это ветвление одной формулой через одну переменную(которую бы в будущем вынес в конфиг) я не смог.

FIL ★★★★
() автор топика
Последнее исправление: FIL (всего исправлений: 1)

Ну в данном случае проще всего

result = eval('float1 if VALUE <= 10 else float2')

В более сложных случаях помни, что по умолчанию exec и eval исполняют код в текущем scope, поэтому можно просто присвоить внешней переменной значение (может понадобиться nonlocal, я точно не помню). Кроме того, можно передать собственные словари в качестве параметров globals и locals и потом смотреть, что в них окажется.

proud_anon ★★★★★
()
Ответ на: комментарий от FIL

Ну тут очень большое ветвление на строк 30 и константы в нем могут менятся. В основном коде(в репозитории) это хранить не вариант. Поэтому на ум приходит только eval.

Мне кажется, ты что-то неправильное задумал.

Описать это ветвление одной формулой через одну переменную(которую бы в будущем вынес в конфиг) я не смог.

Что ж за ветвление за такое? И почему нельзя вынести все эти меняющиеся константы в конфиг?

proud_anon ★★★★★
()
Последнее исправление: proud_anon (всего исправлений: 1)
Ответ на: комментарий от proud_anon

Ну в данном случае проще всего

Ну это однострочник, а как в множество строк?

FIL ★★★★
() автор топика
Ответ на: комментарий от proud_anon
if VAL <= 5:
    0.1
elif VAL <= 10:
    0.5
elif VAL <= 15:
    0.7
elif VAL <= 20:
    1.0
elif VAL <= 25:
    1.25
elif VAL <= 30:
    1.5
...

Ну вот что-то типо этого и все константы могут менятся(с которыми сравниваем, которые возвращаем). Конфиг будет для меня менее читабельный, чем питонокод.

FIL ★★★★
() автор топика
Ответ на: комментарий от proud_anon

поэтому можно просто присвоить внешней переменной значение

Это пробовал, не получилось. Щас попробую с nonlocal.

FIL ★★★★
() автор топика
Ответ на: комментарий от FIL

Как-так не получилось?

exec('a=1;a=2;a=3')
print(a)
3

Если дать длинное-длинное имя, _my_holder_for_exec_return, то никаких проблем не будет.

anonymous
()
Ответ на: комментарий от FIL

Это пробовал, не получилось. Щас попробую с nonlocal.

Я попробовал, получилось SyntaxError: nonlocal declaration not allowed at module level

Потом я проверил такой код:

#!/usr/bin/env python3
def test():
        x = 3
        exec('x = 4')
        print(x)

x = 2
test()
print(x)

y = 3
exec('y = 4')
print(y)

И он выводит:
3
2
4

то есть в глобальном контексте глобальная переменная меняется, а в локальном контексте получается фигня.

Придётся играть с параметрами globals и locals для exec.

proud_anon ★★★★★
()
Ответ на: комментарий от proud_anon

Я попробовал, получилось SyntaxError: nonlocal declaration not allowed at module level

Угу. Аналогично.

Придётся играть с параметрами globals и locals для exec.

Бедапечаль :-(

FIL ★★★★
() автор топика
Ответ на: комментарий от FIL

Я не совсем понял что ты хочешь сделать, но конкретно для данного случая я бы завел словарь вида { 5: 0.1, 10: 0.5 <...>} и циклом пробегал бы по ключам, сравнивая с ними val и присваивая соответсвующее значение - это имхо будет проще и нагляднее, да и менять ключи и значения словаря сможешь когда угодно. Но вообще я не понял что ты хочешь сделать, так что могу нести чушь.

alozovskoy ★★★★★
()

Python3

from collections import OrderedDict

map = OrderedDict((
    (5, 0.1),
    (10, 0.5),
    (15, 0.7),
    (20, 1.0),
    (25, 1.25),
    (30, 1.5),
    #...
))

def val_to_res(val, map):
    for breakpoint in map.keys():
        if val <= breakpoint:
            return map[breakpoint]
    return 42.0

for val in range(50):
    print(val, "->", val_to_res(val, map))

Smola
()
Последнее исправление: Smola (всего исправлений: 2)

Хоть бы один сраный школьник вспомнил про bisect. Смотришь на здешние творения и кровь из глаз.

anonymous
()
Ответ на: комментарий от anonymous

это ты ещё остальной код не видел

anonymous
()
Ответ на: комментарий от FIL

а у тебя в сравнении числа всегда кратны 5? если да, то можно вообще простым списком обойтись, типа [0.1, 0.5, 0.7...][int(VAL//5)]

anonymous
()
Ответ на: комментарий от anonymous

Не ругайтесь, дяди, я же не настоящий сварщик, я маску на стройке нашел!
Вот bisect версия:

def val_to_res(val, map):
    keys = list(map.keys())
    values = list(map.values())
    i = bisect_left(keys, val)
    return values[i] if i < len(values) else 42.0

Smola
()

Короче сделал через popen пока :-(

FIL ★★★★
() автор топика
Ответ на: комментарий от FIL

Зачем вообще городить костыли для запуска питоньего кода из питоньего кода? Или тебе кровь из носа нужно генерировать код в рантайме?

templarrr ★★★★★
()
Ответ на: комментарий от templarrr

1) Чтобы не хранить эти данные в плохо читаемом конфиге.
2) Чтобы не коммитить изменения этого кода по пять раз на дню в репозиторий.

FIL ★★★★
() автор топика
Ответ на: комментарий от Smola

Чтобы итерироваться по ключам можно просто писать for key in dict:

Но в данном случае было бы лучше итерироваться по for key, value in dict.items():

А ещё я вообще не вижу зачем тут ordereddict, можно было бы просто сделать tuple of tuples.

pawnhearts ★★★★★
()
Ответ на: комментарий от pawnhearts

Чтобы итерироваться по ключам можно просто писать for key in dict

Что лучше, меньше кода или читабельность.

FIL ★★★★
() автор топика
Ответ на: комментарий от pawnhearts

Как правильно заметил анон, лучше использовать bisect (O(log2 N)), чем полный перебор (O(N)).

from bisect import bisect_left

PAIRS = (
    (5, 0.1),
    (10, 0.5),
    (15, 0.7),
    (20, 1.0),
    (25, 1.25),
    (30, 1.5),
)

breakpoints = [pair[0] for pair in PAIRS]

def val_to_res(val, breakpoints):
    i = bisect_left(breakpoints, val)
    return PAIRS[i][1] if i < len(PAIRS) else 42.0

for val in range(33):
    print(val, "->", val_to_res(val, breakpoints))

Smola
()
Ответ на: комментарий от Smola

Если список меньше сотни, то вообще пофиг.

anonymous
()
Ответ на: комментарий от Smola

Ещё некруто называть перменные именами встроенных функций/типов. Типа map.

pawnhearts ★★★★★
()
Ответ на: комментарий от proud_anon

Вот сейчас получилось! Спасибо.
Вот globals даже можно локально объявлять, тоже работает :-)

FIL ★★★★
() автор топика
Ответ на: комментарий от FIL

Вот globals даже можно локально объявлять, тоже работает :-)

Конечно, ведь это просто словарь, передаваемый в качестве параметра для exec, где именно он хранится не важно.

proud_anon ★★★★★
()

Дословно же:

def function():
  exec условия

rez = function()
где «условия» =
if VALUE < 10: return ФЛОАТ
else: return ДРУГОЙ_ФЛОАТ
...
так globals не нужен.

DonkeyHot ★★★★★
()
Последнее исправление: DonkeyHot (всего исправлений: 1)
Ответ на: комментарий от FIL
CL-USER> (defmacro many-ifs (val conds)
           (let ((v (gensym)))
             `(let ((,v ,val))
                (cond ,@(loop for (a b) in conds
                           collect `((<= ,v ,a) ,b))))))
MANY-IFS
CL-USER> (macroexpand-1 '(many-ifs 12 ((5 0.1) (10 0.5)
                                       (15 0.7) (20 1.0)
                                       (25 1.25) (30 1.5))))
(LET ((#:G840 12))
  (COND ((<= #:G840 5) 0.1) ((<= #:G840 10) 0.5) ((<= #:G840 15) 0.7)
        ((<= #:G840 20) 1.0) ((<= #:G840 25) 1.25) ((<= #:G840 30) 1.5)))
T

loz ★★★★★
()
Ответ на: комментарий от loz

Ну куда же без лиспопетушни на которой кодит 1.5 человека.
Кстати, ваша штука еще менее читабельнее всего остального. Аж глаза вытекли.

FIL ★★★★
() автор топика
Последнее исправление: FIL (всего исправлений: 2)
Ответ на: комментарий от FIL

Она единственная делает то что тебе надо без попыток навязать «правильный» способ.

ваша штука еще менее читабельнее всего остального. Аж глаза вытекли.

привыкнешь)

loz ★★★★★
()
Последнее исправление: loz (всего исправлений: 1)
Ответ на: комментарий от FIL

Почему тебя не устраивает вынести

PAIRS = (
    (5, 0.1),
    (10, 0.5),
    (15, 0.7),
    (20, 1.0),
    (25, 1.25),
    (30, 1.5),
)
в одельный конфигурационный файл, например, my_config.py?
В конфиге нет кода, только данные. Можешь передавать конфиг как аргумент app.py -c /path/to/my_config.py или брать его из переменных окружения CONFIG=/path/to/my_config.py app.py.

Smola
()
Ответ на: комментарий от FIL

Ну тут очень большое ветвление на строк 30 и константы в нем могут менятся

константы в нем могут менятся

константы
менятся

Что-то не так, не кажется?

mcgeek
()
Ответ на: комментарий от mcgeek

Не, все нормально, это рантайм константы, а при компиляции они берутся откуда надо.

loz ★★★★★
()
Ответ на: комментарий от FIL

Сказано же, что условий в коде не должно быть

Так его там и нет. В коде def, exec и ссылка на внешние условия(open(filename), например).

DonkeyHot ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.