【问题标题】:Creating a simple scripting language in Python在 Python 中创建简单的脚本语言
【发布时间】:2011-06-30 23:02:35
【问题描述】:

我正在创建一个可以监视和操作消息流的 GUI 应用程序。我正在尝试创建一种简单的方法来让用户编写其中一些功能的脚本,并且我正在寻找可能的候选人。最初我想使用 XML,因为它可以自然地处理嵌入式代码:

<if>
   <condition>
      <recv>
         <MesgTypeA/>
      </recv>
   </condition>
   <loop count=10>
      <send>
         <MesgTypeB>
            <param1>12</param1>
            <param2>52</param2>
         </MesgTypeB>
      </send>
   </loop>
</if>

对于解析,我计划使用 ElementTree 并从代码中构建状态。编写和阅读 XML 并不是一件容易的事,尤其是因为我不能假设脚本的编写者会有任何经验。我想知道是否有人有任何更容易在 Python 中读/写和处理的替代方案。我研究了 JSON,但因为它是一个脚本,所以顺序很重要。

谁能提出任何可能的替代方案?

谢谢。

【问题讨论】:

    标签: python xml scripting


    【解决方案1】:

    Python itself 怎么样?

    例如:

    >>> import code
    >>> def host_func():
    ...     print("Hello old chap!")
    ...
    >>> c = code.compile_command("print(\"Script says hello!\"); host_func()")
    >>> exec(c)
    Script says hello!
    Hello old chap!
    

    exec 让您通过两个可选参数localsglobals 明确说明要从主机环境中公开的内容。

    在这个例子中,我明确说明了脚本可以访问哪些全局变量。请注意,我可以在这里“创建”变量,或者给现有函数另一个名称。它是一个指向函数和数据的字典。

    >>> import code
    >>> def secret():
    ...     print("What?! I don't even... get out of here.")
    ...
    >>> def public():
    ...     print("Hello stranger.")
    ...
    >>> c = code.compile_command("secret(); public()")
    

    用包含两个函数的全局变量调用它,指向已经存在的函数给出:

    >>> exec(c, {"secret": secret, "public": public})
    What?! I don't even... get out of here.
    Hello stranger.
    

    现在当我省略 secret 时,脚本将无法再找到它。

    >>> exec(c, {"public": public})
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<input>", line 1, in <module>
    NameError: name 'secret' is not defined
    

    在这里我重新定义secret一起:

    >>> exec(c, {"public": public, "secret":lambda: print("Haha! Doppelganger.")})
    Haha! Doppelganger.
    Hello stranger.
    

    正如lazyr 在 cmets 中提到的,存在安全问题。上面的例子让脚本几乎可以做它想做的事。在某些情况下,这是不可接受的。

    有一些事情可以阻止它:

    • 中性__builtins__,仅允许“列入白名单”的内置函数。
    • 使导入模块变得困难。

    例如,下面是你如何使用 import 语句(在 Py2.* 内置函数中是 __builtins__):

    >>> import builtins
    >>> def no_import(*args, **kwargs):
    ...     raise ImportError("I cannot let you do that, Dave.")
    ...
    >>> builtins.__import__ = no_import
    >>> import os
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 2, in no_import
    ImportError: I cannot let you do that, Dave.
    

    由此我们可以在 globals 参数中传递我们自己的builtins

    >>> import code
    >>> evil_code = "import os; import stat; os.chmod(\"passwords.txt\", stat.S_IROT
    H);"
    >>> compiled = code.compile_command(evil_code)
    >>> def no_import(*args, **kwargs):
    ...    raise ImportError("I cannot let you do that, Dave.")
    ...
    >>> exec(compiled, {"__builtins__": {"__import__": no_import}})
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<input>", line 1, in <module>
      File "<stdin>", line 2, in no_import
    ImportError: I cannot let you do that, Dave.
    

    但是,需要注意的是,这将阻止在它之后发生的所有导入。将其替换为可让您导入列入白名单的模块的版本可能会更好。

    最后,我不确定这是否能完全保护你。一些狡猾的人很可能会绕过它。但至少应该劝阻最公然的违规行为。

    【讨论】:

    • +1 这样你就超过了 5000 名声望里程碑。也许您应该提及一些有关安全问题的信息? open("/important/file", "w").write("stuff")
    • @lazyr:哦,谢谢 :D... 我会用蛋糕庆祝。是的,我正在考虑这个问题。我可以补充一点。
    • 文字墙对我造成 +400 伤害。
    • +1 当我看到你的图标并以为你是我时让我感到震惊......哦,以及很好的答案。
    • @NickAldwin,欢迎来到俱乐部。现在 SO 上有三个 Merkwürdigliebes :)
    【解决方案2】:

    您可以使用pyparsing 定义自己的脚本语言语法。

    【讨论】:

      【解决方案3】:

      Python 或者 Lisp,因为语法很容易解析。

      【讨论】:

        猜你喜欢
        • 2011-05-02
        • 2011-01-27
        • 1970-01-01
        • 1970-01-01
        • 2010-10-10
        • 2011-11-02
        • 1970-01-01
        • 2013-02-20
        • 1970-01-01
        相关资源
        最近更新 更多