【问题标题】:Using exec() to make and assign variables from a text file使用 exec() 从文本文件中创建和分配变量
【发布时间】:2020-10-29 21:51:16
【问题描述】:

这个问题是the question I asked here 的后续问题,总结起来是:

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

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) 按预期返回 512

我的问题是:

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

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

【问题讨论】:

  • 使用exec() 通常被认为是不安全的(潜在的安全隐患取决于输入的来源)。动态创建变量是一种糟糕的 Python 编程实践——尽管它是可能的。见How do I create variable variables?
  • 我建议你使用 Python 的 configparser 模块来做你想做的事。
  • 感谢 martineau 和 GordonAitchJay,我现在了解了安全风险(在这种情况下我并不担心,因为这些代码都没有面向外界)。但是除了安全性之外,如果我像这样使用exec() 声明变量,是否存在与 python 如何跟踪变量、垃圾收集、堆栈的不当使用等有关的任何“代码安全”问题?
  • 我不知道。

标签: python string exec


【解决方案1】:

(1) 这种方法安全吗?

不,这不安全。如果有人可以编辑/控制/替换params.txt,他们就可以将其制作成允许在运行脚本的机器上执行任意代码。

这真的取决于运行 Python 脚本的地点和人员,以及他们是否可以修改 params.txt。如果它只是一个用户直接在普通计算机上运行的脚本,那么没有什么可担心的,因为他们已经可以访问机器并且可以做他们想做的任何恶意事情,而不必使用你的 Python 脚本来做。

(2) 如果我想将上面的代码封装在一个函数中,例如read_params(),只是把最后一行改成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 脚本只能由您信任的用户在普通计算机上运行,​​这并不重要。他们已经可以做恶意的事情,只需以普通用户的身份访问计算机。


有没有 configparser 的替代品?

我不确定。对于您的用例,我认为您不必担心太多。自己动手吧。

这与您其他问题中的一些答案类似,但它使用 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]}

【讨论】:

  • 感谢您的解决方案,以及出人意料的嘻哈音乐(我上次在 90 年代中期在 Anthrax 专辑中听到的极其出人意料的样本!)原则上我无法访问写params.txtconfigparser 有替代品吗?或者在那种情况下,解决方案是读入params.txt,检查输入是否符合预期,然后以正确的格式编写配置文件,然后再读回?完成一项简单的任务似乎还有很长的路要走!
  • 哈哈不用担心。你是对的,这样做似乎很麻烦。我编写的read_params 函数应该可以完成这项工作。至少它适用于您发布的一小部分params.txt
  • @jms547:仅供参考,ConfigObj 模块是configparser 的一个很好的替代品。
猜你喜欢
  • 2017-03-08
  • 1970-01-01
  • 2020-01-31
  • 2022-08-19
  • 2016-02-28
  • 2019-07-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多