Функция exec() позволяет нам выполнить блок кода Python из строки. Эта встроенная функция Python может пригодиться, когда нам нужно запустить динамически сгенерированный код. Но использовать ее нужно с умом, так как с ней связаны некоторые риски безопасности.
Содержание
- Что собой представляет функция exec()
- Первое знакомство с exec()
- Выполнение кода из строкового ввода
- Выполнение кода из файла Python
- Параметры globals и locals
- Заключение
Что собой представляет функция exec()
Функция exec() позволяет нам выполнить любой фрагмент кода Python, независимо от того, насколько он велик или мал. Ее помощью можно выполнять динамически генерируемый код.
Представьте интерпретатор Python, который берет кусок кода, обрабатывает его внутри и выполняет. Функция exec() делает то же самое. Она похожа на независимый интерпретатор Python.
Функция exec() способна выполнять как простые скрипты, так и полноценные программы на Python. Она может выполнять вызовы и определения функций, определение и инстанцирование классов, импорт и многое другое.
Синтаксис
exec(object [ , globals [ , locals]])
object
должен быть строкой или объектом кода. Если это строка, то она будет разобрана как набор предложений Python, который будет выполнен, если не возникнет синтаксической ошибки. Если это объект кода, то он просто выполняется.globals
иlocals
позволяют предоставить словари, представляющие глобальное и локальное пространства имен.
Возвращаемое значение
Возвращаемое значение функции exec() — None
. Это может быть связано с тем, что каждый отдельный фрагмент кода не имеет конечного результата.
Первое знакомство с exec()
Чтобы понять, как работает exec()
, давайте рассмотрим пример кода:
obj = ["apple", "cherry", "melon", "strawberry"] code = "print([sliced[:4] for sliced in obj if 'a' not in sliced])" exec(code) ......... ['cher', 'melo']
И еще один пример:
# Этот код будет работать постоянно, и мы сможем запускать наш код так же, # как делали бы это в интерпретаторе Python while True: exec(input(">>> "))
>>> print("Welcome to GeekPython") Welcome to GeekPython >>> import numpy as np >>> print(np.random.randint(16, size=(2, 4))) [[11 13 3 13] [ 7 6 15 5]] >>> x = ["apple", "banana", "cherry", "mango"] >>> print([fruit for fruit in x if "a" not in fruit]) ['cherry']
Функция exec() работает точно так же, как интерпретатор Python, принимая наш код, обрабатывая его, выполняя и возвращая правильные результаты.
Мы запускаем бесконечный цикл, а внутри него принимаем ввод из командной строки и оборачиваем его в функцию exec() для выполнения.
Выполнение кода из строкового ввода
Мы можем использовать функцию exec() для выполнения кода в формате строки.
Создать строковый ввод можно несколькими способами. Для того можно использовать отдельные строки кода, а также код в несколько строк (с применением символов новой строки или тройных кавычкех с правильным форматированием).
Использование однострочников
В Python однострочный код, также известный как однострочник, — это код, написанный в одной строке, который может выполнять несколько задач одновременно.
Вот пример однострочника:
obj = ["apple", "cherry", "melon", "strawberry"] print([sliced[:4] for sliced in obj if 'a' not in sliced])
['cher', 'melo']
Если мы запустим этот код с помощью exec()
, выглядеть это будет так:
obj = ["apple", "cherry", "melon", "strawberry"] exec("print([sliced[:4] for sliced in obj if 'a' not in sliced])") #-----------------------------ИЛИ-------------------------------- exec("code = [sliced[:4] for sliced in obj if 'a' not in sliced]")
Второй вариант однострочника с использованием exec()
ничего не выведет при выполнении. Вместо этого результат будет сохранен в переменной code
для последующего доступа.
Выполнение нескольких строк кода, разделенных символами новой строки
Мы можем объединить несколько предложений в однострочную строку с помощью символов новой строки \n
.
exec("square = int(input('Enter the number: '))\nprint(f'The square of {square}:', square**2)")
Enter the number: 30 The square of 30: 900
Символ новой строки (\n
) используется для того, чтобы функция exec() воспринимала наш однострочный строковый код как многострочный набор предложений Python.
Использование строки в тройных кавычках
В Python мы часто используем тройные кавычки для комментирования или документирования кода. Однако в данном случае мы будем использовать их для генерации строкового ввода, который выглядит и ведет себя точно так же, как обычный код Python.
Код, который мы пишем в тройных кавычках, должен иметь правильные отступы и форматирование, так же, как и обычный код Python. Пример:
sample_code = """ integers = [4, 7, 2, 9, 44] def square(num): return num ** 2 def odd_num(num): return num % 2 == 1 square_if_even = [square(number) for number in integers if number % 2 == 0] odd_number = [number for number in integers if odd_num(number)] print("Original values:", integers) print("Square of even number:", square_if_even) print("Odd number:", odd_number) """ exec(sample_code)
Original values: [4, 7, 2, 9, 44] Square of even number: [16, 4, 1936] Odd number: [7, 9]
Приведенный код похож на обычный код Python. Он правильно отформатирован, имеет все нужные отступы. Но он также заключен в тройные кавычки и в итоге сохранен как строковый ввод в переменной sample_code
, которая затем передается на выполнение в функцию exec().
Выполнение кода из файла Python
С помощью функции exec() можно выполнить код, сохраненный в файле с расширением .py
. Для этого нужно прочитать содержимое файла, используя функцию open()
.
Рассмотрим пример. Файл sample.py содержит следующий код:
# sample.py def anime(name): print(f"Favourite anime: {name}") anime("One Piece") list_of_anime = input("Enter your favourite anime: ").split(",") print("Your Favourite anime:", list_of_anime)
Первый блок кода просто выводит название аниме. Следующий блок принимает в качестве входных данных названия ваших любимых аниме, разделенные запятыми, и выводит их в виде списка.
А теперь давайте выполним этот файл с помощью функции exec().
with open("sample.py", mode="r") as sample: file = sample.read() exec(file)
Favourite anime: One Piece Enter your favourite anime: One Piece, Naruto, Demon Slayer, Jujutsu kaisen Your Favourite anime: ['One Piece', ' Naruto', ' Demon Slayer', ' Jujutsu kaisen']
Мы использовали функцию open()
с помощью оператора with
, чтобы открыть файл .py как обычный текстовый файл. Затем мы использовали .read()
на объекте file
, чтобы прочитать содержимое файла как строку в переменную file
, которая передается в exec
для выполнения кода.
Параметры globals и locals
Эти параметры опциональны. С их помощью можно ограничить использование функций, методов и переменных, которые не являются обязательными.
Поскольку эти параметры необязательны, их отсутствие заставляет функцию exec() выполнять входной код в текущей области видимости. Для лучшего понимания рассмотрим следующий пример.
code = """ out = str1 + str2 print(out) """ # Глобальные переменные str1 = "Hel" str2 = "lo" exec(code)
Hello
Этот код успешно выполнился и выдал результат, объединяющий обе глобальные переменные. Поскольку параметры globals
и locals
не были указаны, функция exec() выполнила введенный код в текущей области видимости. В данном случае текущая область видимости является глобальной.
Давайте теперь немного изменим код:
code = """ out = str1 + str2 print(out) """ # Глобальные переменные str1 = "Hel" str2 = "lo" print(out)
Traceback (most recent call last): ..... NameError: name 'out' is not defined. Did you mean: 'oct'?
В этом примере мы попытались получить доступ к значению переменной out
перед вызовом exec() и получили ошибку.
Однако мы можем получить доступ к значению переменной out
после вызова exec()
, поскольку после этого вызова переменная, определенная в коде инпута, будет доступна в текущей области видимости.
exec(code) print(out)
Hello Hello
Использование параметров globals и locals
code = """ out = str1 + str2 print(out) """ # Глобальные переменные str1 = "Hel" str2 = "lo" exec(code, {"str1": str1})
Traceback (most recent call last): .... NameError: name 'str2' is not defined. Did you mean: 'str1'?
Этот код возвращает ошибку, так как мы не определили ключ, содержащий str2
в словаре, поэтому exec()
не имеет к нему доступа.
code = """ out = str1 + str2 print(out) """ # Глобальные переменные str1 = "Hel" str2 = "lo" exec(code, {"str1": str1, "str2": str2}) print(out)
Hello Traceback (most recent call last): .... NameError: name 'out' is not defined. Did you mean: 'oct'?
Теперь функция exec() имеет доступ к обеим глобальным переменным, и код возвращает вывод без ошибки. Но на этот раз у нас нет доступа к out
после вызова exec()
, потому что мы используем пользовательский словарь для предоставления области выполнения для exec()
.
Вот пример совместного использования locals
и globals
:
code = """ out = str1 + str2 + " " + x print(out) """ # Глобальные переменные str1 = "Hel" str2 = "lo" def local(): # Локальная переменная x = "there" exec(code, {"str1": str1, "str2": str2}, {"x": x}) local()
Hello there
Здесь мы вызвали exec()
из локальной функции. В глобальной области видимости у нас есть глобальные переменные, а в локальной области видимости (на уровне функции) — локальная. Параметр globals
задает переменные str1
и str2
, а параметр locals
— переменную x
.
Блокирование ненужных методов и переменных
Используя параметры globals
и locals
, мы можем контролировать, ограничивать или использовать какую-либо переменную или метод при вводе кода. В этом разделе мы ограничим некоторые функции модуля datetime
в Python.
from datetime import * code = """ curr_time = datetime.now() print(curr_time) """ exec(code, {})
Traceback (most recent call last): .... NameError: name 'datetime' is not defined
Мы ограничили использование метода datetime
из модуля datetime
, что привело к ошибке.
Использование необходимых методов и переменных
Мы можем использовать только те методы, которые требуются при выполнении exec()
.
from datetime import * # Разрешить только два метода allowed_param = {'datetime': datetime, 'timedelta': timedelta} exec("print(datetime.now())", allowed_param) exec("print(datetime.now() + timedelta(days=2))", allowed_param) # Метод date() не разрешен exec("print(date(2022, 9, 4))", allowed_param)
2022-10-15 18:40:44.290550 2022-10-17 18:40:44.290550 Traceback (most recent call last): .... NameError: name 'date' is not defined
Ошибка возникла из-за того, что метод date не был разрешен. За исключением двух методов, datetime
и timedelta
, все методы в модуле datetime
были запрещены.
Давайте посмотрим, чего еще можно добиться с помощью параметров globals
и locals
.
from datetime import * # Установить параметр globals в __builtins__ globals_param = {'__builtins__': __builtins__} # Установить параметр locals на прием только print(), slice() aиnd dir() locals_param = {'print': print, 'dir': dir, 'slice': slice} exec('print(slice(2))', globals_param, locals_param) exec('print(dir())', globals_param, locals_param)
slice(None, 2, None) ['dir', 'print', 'slice']
Внутри функции exec() можно выполнить только метод slice()
и все встроенные методы. Несмотря на то что метод slice()
не из модуля datetime
, здесь он отлично работает.
Мы также можем ограничить использование __builtins__
, установив для него значение None
.
from datetime import * # Установить параметр globals в None globals_param = {'__builtins__': None} # Установить параметр locals на прием только print(), slice(), sum() и dir() locals_param = {'print': print, 'dir': dir, 'slice': slice, 'sum': sum} # Директория разрешенных методов exec('print(dir())', globals_param, locals_param) exec('print(f"Sum of numbers: {sum([4, 6, 7])}")', globals_param, locals_param)
['dir', 'print', 'slice', 'sum'] Sum of numbers: 17
Мы ограничили использование __builtins__
, поэтому не можем использовать встроенные методы и можем выполнить только методы print
, sum
, slice
и dir
внутри exec()
.
Заключение
Из этой статьи вы узнали, как использовать встроенную функцию Python exec() для выполнения кода из строкового ввода. Она позволяет запускать динамически генерируемый код Python. Мы разобрали:
- Что такое функция exec() и как с ней работать
- Выполнение кода Python из строкового ввода и исходных файлов Python
- Использование параметров
globals
иlocals
.
Кроме того, мы рассмотрели несколько примеров, которые помогли вам лучше понять функцию exec().
Перевод статьи “How to Use exec() in Python — Everything You Need to Know”.