使用 exec() 从文本文件创建和分配变量

Using exec() to make and assign variables from a text file

此问题是 的后续问题,总结起来是:

"在 python 中,我如何从文本文件 params.txt 中读取参数,创建变量并为它们分配文件中的值?文件的内容是(请忽略自动语法高亮,params.txt实际上是一个纯文本文件):

Lx = 512 Ly = 512
g = 400
================ Dissipation =====================
nupower = 8 nu = 0
...[etc]

并且我希望我的 python 脚本读取文件,以便我可以将 Lx、Ly、g、nupower、nu 等用作变量(不是字典中的键),并在 params.txt。顺便说一句,我是 python 新手。"

在帮助下,我想出了以下使用 exec() 的解决方案:

with open('params.txt', 'r') as infile:
    for line in infile:
        splitline = line.strip().split(' ')
        for i, word in enumerate(splitline):
            if word == '=':
                exec(splitline[i-1] + splitline[i] + splitline[i+1])

这行得通,例如print(Lx) returns 512 符合预期。

我的问题是:

(1) 这种方法安全吗?大多数提到 exec() 函数的问题的答案都包含有关其使用的可怕警告,并暗示除非您真的知道自己在做什么,否则不应使用它。如前所述,我是新手,所以我真的不知道自己在做什么,所以我想检查一下这个解决方案是否会给自己带来麻烦。脚本的其余部分使用从该文件读取的变量和其他文件的数据进行一些基本分析和绘图。

(2) 如果我想将上面的代码包装在一个函数中,例如read_params(),只是把最后一行改成exec(splitline[i-1] + splitline[i] + splitline[i+1], globals())吗?我知道这会导致 exec() 在全局命名空间中进行分配。我不明白的是这是否安全,如果不安全,为什么不。 (新手见上文!)

(1) Is this approach safe?

不,这不安全。如果有人可以 edit/control/replace params.txt,他们可以制作它以允许在机器上执行任意代码 运行 脚本。

这实际上取决于 运行 您的 Python 脚本的位置和人员,以及他们是否可以修改 params.txt。如果它只是用户直接在普通计算机上编写的脚本 运行,那么就没什么好担心的,因为他们已经可以访问该计算机并且可以做任何他们想做的恶意事情,而不必使用你的 Python 脚本。

(2) If I want to wrap up the code above in a function, e.g. read_params(), is it just a matter of changing the last line to exec(splitline[i-1] + splitline[i] + splitline[i+1], globals())?

正确。它不会改变您可以执行任意代码的事实。

假设这是 params.txt:

Lx = 512 Ly = 512
g = 400
_ = print("""Holy\u0020calamity,\u0020scream\u0020insanity\nAll\u0020you\u0020ever\u0020gonna\u0020be's\nAnother\u0020great\u0020fan\u0020of\u0020me,\u0020break\n""")
_ = exec(f"import\u0020ctypes")
_ = ctypes.windll.user32.MessageBoxW(None,"Releasing\u0020your\u0020uranium\u0020hexaflouride\u0020in\u00203...\u00202...\u00201...","Warning!",0)
================ Dissipation =====================
nupower = 8 nu = 0

这是你的脚本:

def read_params():
    with open('params.txt', 'r') as infile:
        for line in infile:
            splitline = line.strip().split(' ')
            for i, word in enumerate(splitline):
                if word == '=':
                    exec(splitline[i-1] + splitline[i] + splitline[i+1], globals())

read_params()

如您所见,它已正确分配了您的变量,但它还调用了 print,导入了 ctypes 库,然后向您显示了一个对话框,让您知道您的小后院浓缩设施已受挫。

正如 martineau 所建议的,您可以使用 configparser。您必须修改 params.txt 以便每行只有一个变量。

tl;dr:使用 exec 是不安全的,也不是最佳实践,但是如果您的 Python 脚本在普通计算机上仅 运行 则没关系您信任的用户。他们已经可以做一些恶意的事情,只需以普通用户的身份访问计算机即可。


Is there an alternative to configparser?

我不确定。有了你的 use-case,我认为你不必担心太多。自己动手吧。

这与您在其他问题中的某些答案类似,但使用了 literal_eval 并更新了 globals 字典,因此您可以根据需要直接使用变量。

params.txt:

Lx = 512 Ly = 512
g = 400
================ Dissipation =====================
nupower = 8 nu = 0
alphapower = -0 alpha = 0
================ Timestepping =========================
SOMEFLAG = 1
SOMEOTHERFLAG = 4
dt = 2e-05
some_dict = {"key":[1,2,3]}
print = "builtins_can't_be_rebound"

脚本:

import ast

def read_params():
    '''Reads the params file and updates the globals dict.'''
    _globals = globals()
    reserved = dir(_globals['__builtins__'])
    with open('params.txt', 'r') as infile:
        for line in infile:
            tokens = line.strip().split(' ')
            zipped_tokens = zip(tokens, tokens[1:], tokens[2:])
            for prev_token, curr_token, next_token in zipped_tokens:
                if curr_token == '=' and prev_token not in reserved:
                    #print(prev_token, curr_token, next_token)
                    try:
                        _globals[prev_token] = ast.literal_eval(next_token)
                    except (SyntaxError, ValueError) as e:
                        print(f'Cannot eval "{next_token}". {e}. Continuing...')

read_params()

# We can now use the variables as expected
Lx += Ly
print(Lx, Ly, SOMEFLAG, some_dict)

输出:

1024 512 1 {'key': [1, 2, 3]}