使用Python的exec()动态生成代码
Python 中的函数exec()
允许我们从字符串执行 Python 代码块。当我们需要运行动态生成的Python代码时,Python中的这个内置函数可以派上用场,但建议不要随意使用它,因为它会带来一些安全风险。
在本教程中,我们将学习
如何使用 Python 的exec()函数
如何使用exec()函数执行 Python 代码以及代码示例
从字符串和 python 源文件执行 Python 代码
使用
globals
及locals
参数
Python 的 exec() 函数
Python 的exec()
函数允许我们执行任何一段 Python 代码,无论该代码有多大或多小。这个函数帮助我们执行动态生成的代码。
想象一个Python解释器,它接受一段代码,在内部处理它,然后执行它,exec()函数做同样的事情。它就像一个独立的Python解释器。
exec ()能够执行简单的 Python 程序以及功能齐全的 Python 程序。它可以执行函数调用和定义、类定义和实例化、导入等等。
句法
exec(object [ , globals [ , locals]])
object
- 它必须是字符串或代码对象。如果它是一个字符串,它将被解析为一组 Python 语句,除非发生语法错误,否则将执行该语句。如果它是一个代码对象,那么它将简单地执行。globals
-locals
这使我们能够提供代表全局和本地命名空间的字典。
返回值
exec()函数的返回值为None。可能是因为每一段代码都没有最终的结果。
初见
以下是exec()函数工作原理的初步了解。
obj = ["apple", "cherry", "melon", "strawberry"]
code = "print([sliced[:4] for sliced in obj if 'a' not in sliced])"
exec(code)
.........
['cher', 'melo']
使用exec()函数的另一个示例
# The code will continuously run and we can run our code as we do in
# Python interpreter
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']
它的行为与Python解释器完全相同,它接受我们的代码,在内部处理它,执行它,并返回正确的结果。
我们正在运行一个无限循环,在其中,我们在命令行中获取用户的输入,并执行我们用exec()函数包装的输入。
从字符串输入执行代码
我们可以使用exec()函数来执行字符串格式的代码。我们可以使用多种方法来构建基于字符串的输入:
使用单行代码
使用换行符
使用具有正确格式的三引号字符串
使用基于单行字符串的输入
Python 中的单行代码或单行代码包含用一行编写的代码,能够同时执行多个任务。
如果我们编写单行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])")
#-----------------------------OR--------------------------------
exec("code = [sliced[:4] for sliced in obj if 'a' not in sliced]")
如果我们执行上面编写的其他代码,则不会返回任何内容,而是将输出存储在变量中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 代码,但唯一的区别是它用三引号括起来,使其基于字符串的输入存储在变量中,然后使用 exec()sample_code
函数执行。
从 Python 文件执行代码
我们可以使用exec()函数通过使用open().py
函数读取文件内容来执行 Python() 源文件中的代码。
如果我们看一下示例,其中有一个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 函数执行 Python 源文件
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()
using 语句的函数with
将文件作为常规文本文件打开.py
,然后使用.read()
文件对象上的 来将文件内容作为字符串读取到在execfile
中传递的变量中以执行代码。
使用globals
及locals
参数
这些参数是可选的。我们可以使用globals
和locals
参数来限制各种可能不需要的函数、方法和变量的使用。
由于这些参数是可选的,如果我们省略它们,那么exec()函数将在当前范围内执行输入代码。看下面的例子可以更好地理解它。
code = """
out = str1 + str2
print(out)
"""
# global variables
str1 = "Hel"
str2 = "lo"
exec(code)
输出
Hello
上面的代码成功运行并打印了结合两个全局变量的输出。发生这种情况是因为未指定globals
和参数,因此exec()函数在当前范围内运行代码输入。在这种情况下,当前范围是global。locals
下面是一个示例,显示如何访问当前范围内代码输入的变量值。
code = """
out = str1 + str2
print(out)
"""
# global variables
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()后,代码输入中定义的变量将在当前作用域中可用。
exec(code)
print(out)
输出
Hello
Hello
使用globals
及locals
参数
code = """
out = str1 + str2
print(out)
"""
# global variables
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)
"""
# global variables
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
and的示例globals
code = """
out = str1 + str2 + " " + x
print(out)
"""
# global variables
str1 = "Hel"
str2 = "lo"
def local():
# local variable
x = "there"
exec(code, {"str1": str1, "str2": str2}, {"x": x})
local()
输出
Hello there
我们在上面的代码中调用了exec()local
函数。我们在全局作用域中有全局变量,在局部作用域(函数级别)有局部变量。变量str1
和在参数str2
中指定globals
,变量x
在参数中指定locals
。
阻止不必要的方法和变量
globals
我们可以使用和参数控制是否在代码输入中限制或使用任何变量或方法locals
。这里我们将限制Python模块中的一些函数datetime
。
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 *
# Allowing only two methods
allowed_param = {'datetime': datetime, 'timedelta': timedelta}
exec("print(datetime.now())", allowed_param)
exec("print(datetime.now() + timedelta(days=2))", allowed_param)
# date() method is not allowed
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
均不允许使用。datetime
timedelta
globals
让我们看看使用和params还能做什么locals
。
from datetime import *
# Setting globals parameter to __builtins__
globals_param = {'__builtins__': __builtins__}
# Setting locals parameter to take only print(), slice() and 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']
只有该slice()
方法以及所有内置方法可以在exec()函数内执行。在这里您可以注意到该slice()
方法工作正常,即使它不是来自datetime
模块。
__builtins__
我们还可以通过设置其值来限制其使用None
。
from datetime import *
# Setting globals parameter to none
globals_param = {'__builtins__': None}
# Setting locals parameter to take only print(), slice(), sum() and dir()
locals_param = {'print': print, 'dir': dir, 'slice': slice, 'sum': sum}
# Allowed methods directory
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__
,因此我们不能使用内置方法,并且只能在exec()内执行print、sum、slice和dir方法。
结论
我们已经学习了内置 Python exec()函数的使用,该函数允许我们从基于字符串的输入执行代码。它提供运行动态生成的 Python 代码的功能。
- 点赞
- 收藏
- 关注作者
评论(0)