Различия между eval, exec и compile в Python
При работе с Python, встречается необходимость динамического выполнения кода. Для этого в Python предусмотрены три встроенные функции: eval(), exec() и compile(). Однако, для новичка может быть непонятно, в чем же разница между ними и когда какую функцию использовать. В этой статье будет рассмотрено назначение этих функций и их отличия.
Зачастую, при выполнении кода, возникает необходимость динамического создания и выполнения некоторых участков кода. Допустим, имеется строка, которую нужно выполнить как код на Python. Например, ‘print(«Hello, world!»)’ . В этом случае, можно использовать функции eval(), exec() или compile().
Функция eval() используется для вычисления выражений, записанных в виде строк. Например, eval(‘2 + 3’) вернет результат 5 . Однако, eval() не может выполнять сложные конструкции, такие как циклы, условные операторы и т.д.
Наоборот, функция exec() способна выполнять любые конструкции Python. Она принимает строку с кодом Python и выполняет ее. Например, exec(‘for i in range(5): print(i)’) напечатает числа от 0 до 4. Однако, exec() не возвращает результат выполнения кода, только выполняет его.
Функция compile() принимает строку с кодом Python и преобразует ее в объект кода, который затем можно выполнить с помощью exec() или eval(). Особенность compile() в том, что она позволяет указать режим работы: ‘eval’ для вычисления выражений (как eval()) или ‘exec’ для выполнения кода (как exec()). Например, exec(compile(‘for i in range(5): print(i)’, », ‘exec’)) также напечатает числа от 0 до 4.
Таким образом, каждая из функций имеет свое предназначение. eval() подходит для вычисления простых выражений, exec() используется для выполнения сложных конструкций, а compile() позволяет гибко управлять процессом выполнения кода, выбирая нужный режим работы. От выбора функции зависит, какой код можно выполнить и что получить в результате его выполнения.
Сокрытые драгоценности Python
В последнее время у меня появилось новое хобби – чтение документации Python просто для удовольствия! Когда вы читаете на досуге, то, как правило, замечаете интересные «лакомые кусочки», которые в противном случае пропустили бы. Итак, вот перечень «кусочков», которые заставили меня сказать:
О! Вы можете сделать это на Python?
1. Атрибуты функций
Подобно тому, как вы можете устанавливать атрибуты классов и объектов, вы также можете устанавливать атрибуты функций.
def func(x): intermediate_var = x**2 + x + 1 if intermediate_var % 2: y = intermediate_var ** 3 else: y = intermediate_var **3 + 1 # setting attributes here func.optional_return = intermediate_var func.is_awesome = 'Yes, my function is awesome.' return y y = func(3) print('Final answer is', y) # Accessing function attributes print('Show calculations -->', func.optional_return) print('Is my function awesome? -->', func.is_awesome)
Мы установили атрибуты «optional_return» в строке 10 и «is_awesome» в строке 11. Мы получили доступ к этим атрибутам вне функции позднее в строках 19 и 20. Результат кода:
Final answer is 2197
Show calculations —> 13
Is my function awesome? —> Yes, my function is awesome.
Это удобно, когда вы хотите, чтобы функция извлекала некую промежуточную переменную, но не возвращала её явно с оператором return каждый раз при вызове функции. Также обратите внимание, что атрибуты могут быть установлены как внутри определения функции, так и вне определения функции.
2. Цикл for-else
В Python вы можете добавить условие else в цикл for. Условие else будет срабатывать только в том случае, если во время выполнения в теле цикла не встретится оператор break.
my_list = ['some', 'list', 'containing', 'five', 'elements'] min_len = 3 for element in my_list: if len(element) < min_len: print(f'Caught an element shorter than letters') break else: print(f'All elements at least letters long')
All elements at least 3 letters long
Обратите внимание, что else имеет отступ на уровне for, а не на уровне if. Здесь ни один элемент не имеет длины короче трёх. Таким образом, никогда не будет встречен оператор break. Следовательно, условие else будет запущено (после выполнения цикла for) и выведется результат, указанный выше.
Можно утверждать, что этого можно достичь с помощью отдельной переменной, которая отслеживает встретился ли оператор break. И, возможно, для другого человека, читающего код, это будет также менее запутанно. Далее приведен эквивалентный путь достижения того же результата:
my_list = ['some', 'list', 'containing', 'five', 'elements'] min_len = 3 no_break = True for element in my_list: if len(element) < min_len: print(f'Caught an element shorter than letters') no_break = False break if no_break: print(f'All elements at least letters long')
3. Разделители для int
Трудно визуально различить целые числа подобные 10000000 и 100000000 (они даже разные числа?). Мы не можем использовать запятые здесь, в Python, подобному тому, как мы используем их в английском языке, потому что Python интерпретирует это как кортеж из нескольких целых чисел.
У Python есть очень удобный способ справиться с этим: мы можем использовать подчеркивание как разделитель для улучшения читабельности. Таким образом, 1_000_000 будет интерпретироваться как целое число.
a = 3250 b = 67_543_423_778 print(type(a)) print(type(b)) print(type(a)==type(b))
4. eval () и exec ()
В Python есть возможность динамически считывать строку и обрабатывать её как часть Python кода. Это достигается использованием функций eval() и exec() (‘eval’ для вычисления выражений; и ‘exec’ для выполнения операторов).
a = 3 b = eval('a + 2') print('b =', b) exec('c = a ** 2') print('c is', c)
В третьей строке функция eval() считывает входную строку как выражение Python, оценивает её и присваивает результат переменной b. В строке 6 функция exec() считывает входную строку как оператор Python и исполняет её.
Вы даже можете динамически передать созданные строки этим функциям. Например, вы можете создать 1000 переменных с именами х_0, х_1, . х_999 без записи каждого объявления этих переменных вручную в коде. Может показаться, что это совершенно бессмысленно, но это не так.
В целом, в более широком контексте программирования (не только в отношении Python) использование eval/exec невероятно сильно, поскольку позволяет вам писать динамический код, который использует информацию, доступную во время выполнения, для решения проблем, которые не могут быть даже выражены во время компиляции. […] exec – это буквально интерпретатор Python, встроенный в Python, поэтому, если у вас есть особенно сложная проблема для разрешения, один из способов её решить – написать программу для *написания программы для её решения*, затем использовать exec для запуска этой второй программы.
Вы можете прочитать об этом в замечательном объяснении Стивена Д’Апрано.
5. Многоточие (Ellipsis)
Многоточие (или «…») - это встроенная константа Python, аналогичная таким встроенным константам как None, True, False и т.д. Её можно использовать по-разному, например, следующими способами (но, разумеется, не ограничиваясь ими):
5.1. Замена для ненаписанного кода
Подобно pass, константы можно использовать в качестве замены, когда код не полностью написан, но требует некоторого заполнения для синтаксической правильности.
def some_function(): . def another_function(): pass
5.2. Альтернатива NONE
None выбирают, когда хотят обозначить пустой ввод или возврат. Но иногда None может быть одним из ожидаемых входных или возвращаемых параметров функций. В этом случае Многоточие может служить заменой.
# calculate nth odd number def nth_odd(n): if isinstance(n, int): return 2 * n - 1 else: return None # calculate the original n of nth odd number def original_num(m=. ): if m is . print('This function needs some input') elif m is None: print('Non integer input provided to nth_odd() function') elif isinstance(m, int): if m % 2: print(f' is th odd number') else: print(f' is not an odd number') original_num() a = nth_odd(n='some string') original_num(a) b = nth_odd(5) original_num(b) original_num(16)
Функция nth_odd() вычисляет n-ое нечетное число, c учетом n. Функция original_num() вычисляет исходное число n, учитывая n-ое нечетное число. Здесь None – один из ожидаемых входных параметров функции original_num(), так что мы не можем использовать его как замену по умолчанию для аргумента m. Результат кода:
This function needs some input
Non integer input provided to nth_odd() function
9 is 5th odd number
16 is not an odd number
5.3. Нарезка массива в NumPy
NumPy использует многоточие для нарезки массива. Следующий код показывают два эквивалентных способа нарезки:
import numpy as np a = np.arange(16).reshape(2,2,2,2) print(a[. 0].flatten()) print(a[:, :, :, 0].flatten())
[ 0 2 4 6 8 10 12 14]
[ 0 2 4 6 8 10 12 14]
Таким образом, ‘…’ показывает, что существует столько ‘:’, сколько необходимо.
Логическое значение Многоточия
В отличие от None (Логическое значение которого False), логическое значение Многоточия - True.
Итак, я обнаружил следующие интересные особенности.
Атрибуты Функций: присвоение атрибутов функциям, как и объектам.
Цикл for-else: отслеживание, был ли цикл выполнен без оператора break.
Разделители для int: 32_534_478 – это int.
eval() и exec(): читайте строки как код Python и запустите его.
Многоточие: универсальная встроенная константа.
Напутствие
Python – это не только полезный язык, но и действительно интересный. Все мы заняты своей жизнью, но это не мешает узнавать язык ради него самого. Я бы хотел узнать больше о Пасхальных Яйцах, которые вы, возможно, найдёте.