【问题标题】:sandboxing/running python code line by line逐行沙盒/运行python代码
【发布时间】:2012-03-29 01:55:56
【问题描述】:

我希望能够做这两个人正在做的事情:

Inventing on principle @18:20 , Live ClojureScript Game Editor

如果你不想看视频,我的问题是:

假设我有这个代码:

....
xs = []
for x in xrange(10):
    xs.append(x)
...

我想创建一个环境,我可以在其中执行代码、语句的语句并在局部变量/全局变量发生变化时观察/跟踪它们。也许给它一个变量列表以在本地/全局字典中跟踪。就像单步执行代码并保存状态信息一样。

理想情况下,我希望保存每个状态及其关联的上下文数据(本地/全局),以便我可以验证谓词。

我想做一些类似 Bret Victor 的 binarySearch 示例 Inventing on principle @18:20

我说得有道理吗?我觉得用文字解释起来很复杂,但视频展示了我想尝试的内容:)

感谢您的宝贵时间


我尝试/阅读/谷歌搜索的内容:

我的下一步是研究ast 并编译代码并逐位运行它,但我确实需要一些指导。我应该更多地研究反射和inspect-模块吗??

我之前使用过the Spin model checker,但它使用自己的 DSL,我只想用实现语言(在本例中为 python)进行建模。

哦,顺便说一句,我知道沙盒代码的安全隐患,但我并不是要创建一个安全的执行环境,而是要创建一个非常互动的环境,例如针对粗略的模型检查或谓词断言.

【问题讨论】:

  • 调试器能满足你的要求吗?
  • 我不确定。我想以编程方式操作本地/全局,所以如果我可以从调试器中做到这一点。你检查过视频吗? 在原则上发明 @ 18:20 和 1 分钟向前显示了我想要的。
  • 我无法为答案做出太多贡献,但这是一个非常好的问题 (+1)
  • 起初我想,'哦,嘿,我知道我可以推荐一个好的调试器'。我看了视频,然后我想,'python 是否存在类似的东西?那是类固醇的调试器。
  • @Droogans:我已经在 Scheme 中完成了,但是 Lisp 方言更容易,因为语言已经是可解析的形式(S 表达式)并且很容易复制 REPL。快速浏览一下,ClojureScript 示例看起来实际上是这样制作的 :)

标签: python dynamic programming-languages development-environment interpreter


【解决方案1】:

在我的initial successsys.settrace() 之后,我最终切换到the ast module(抽象语法树)。我解析要分析的代码,然后在每次赋值后插入新调用以报告变量名称及其新值。我还插入调用以报告循环迭代和函数调用。然后我执行修改后的树。

        tree = parse(source)

        visitor = TraceAssignments()
        new_tree = visitor.visit(tree)
        fix_missing_locations(new_tree)

        code = compile(new_tree, PSEUDO_FILENAME, 'exec')

        self.environment[CONTEXT_NAME] = builder
        exec code in self.environment

我正在开发像 Bret Victor 这样的实时编码工具,您可以查看我的工作代码 on GitHub,以及它的一些行为示例 in the test。您还可以从the project page 找到演示视频、教程和下载链接。

【讨论】:

  • +1 已接受。这正是我所要求的:) 感谢分享。很抱歉我太忙了,没有时间为它做贡献。
【解决方案2】:

更新:在我初步成功使用此技术后,我转而使用ast 模块,如my other answer 中所述。

sys.settrace() 似乎工作得很好。我使用了您提到的hacks questionAndrew Dalke's article,并让这个简单的示例运行起来。

import sys

def dump_frame(frame, event, arg):
    print '%d: %s' % (frame.f_lineno, event)
    for k, v in frame.f_locals.iteritems():
        print '    %s = %r' % (k, v)
    return dump_frame

def main():
    c = 0
    for i in range(3):
        c += i

    print 'final c = %r' % c

sys.settrace(dump_frame)

main()

我必须解决两个问题才能让它发挥作用。

  1. 如果要继续跟踪,跟踪函数必须返回自身或另一个跟踪函数。
  2. 似乎只在第一次函数调用之后才开始跟踪。我原本没有main方法,直接进入循环。

这是输出:

9: call
10: line
11: line
    c = 0
12: line
    i = 0
    c = 0
11: line
    i = 0
    c = 0
12: line
    i = 1
    c = 0
11: line
    i = 1
    c = 1
12: line
    i = 2
    c = 1
11: line
    i = 2
    c = 3
14: line
    i = 2
    c = 3
final c = 3
14: return
    i = 2
    c = 3
38: call
    item = <weakref at 0x7febb692e1b0; dead>
    selfref = <weakref at 0x17cc730; to 'WeakSet' at 0x17ce650>
38: call
    item = <weakref at 0x7febb692e100; dead>
    selfref = <weakref at 0x7febb692e0a8; to 'WeakSet' at 0x7febb6932910>

【讨论】:

    【解决方案3】:

    听起来你需要 bdb,python 调试器库。它是内置的,文档在这里:http://docs.python.org/library/bdb.html

    它似乎没有您想要的所有功能,但它是开始实施它的明智之选。

    【讨论】:

    • 谢谢老兄 (+1),我会调查 bdb :) 我真的希望我可以用 eval / exec 函数做些什么,或者我是缺少一些东西。
    【解决方案4】:

    好的,伙计们,我已经取得了一些进展。

    假设我们有一个这样的源文件,我们要逐条运行:

    print("single line")
    for i in xrange(3):
        print(i)
        print("BUG, executed outside for-scope, so only run once")
    if i < 0:
        print("Should not get in here")
    if i > 0:
        print("Should get in here though")
    

    我想一次执行一条语句,同时可以访问本地/全局。这是一个快速的概念证明(忽略错误和粗略):

    # returns matched text if found
    def re_match(regex, text):
        m = regex.match(text)
        if m: return m.groups()[0]
    
    # regex patterns
    newline = "\n"
    indent = "[ ]{4}"
    line = "[\w \"\'().,=<>-]*[^:]"
    block = "%s:%s%s%s" % (line, newline, indent, line)
    
    indent_re = re.compile(r"^%s(%s)$" % (indent, line))
    block_re = re.compile(r"^(%s)$" % block)
    line_re =  re.compile(r"^(%s)$" % (line))
    
    buf = ""
    indent = False
    
    # parse the source using the regex-patterns
    for l in source.split(newline):
        buf += l + newline              # add the newline we removed by splitting
    
        m = re_match(indent_re, buf)    # is the line indented?
        if m: 
            indent = True               # yes it is
        else:
            if indent:                  # else, were we indented previously?
                indent = False          # okay, now we aren't
    
        m = re_match(block_re, buf)     # are we starting a block ?
        if m:
            indent = True
            exec(m)
            buf = ""
        else:
            if indent: buf = buf[4:]   # hack to remove indentation before exec'ing
            m = re_match(line_re, buf) # single line statement then?
            if m:
                exec(m) # execute the buffer, reset it and start parsing
                buf = ""
            # else no match! add a line more to the buffer and try again
    

    输出:

    morten@laptop /tmp $ python p.py
    single line
    0
    1
    2
    BUG, executed outside for-scope, son only run once
    Should get in here though
    

    所以这在某种程度上是我想要的。此代码将源代码分解为可执行语句,我可以在语句之间“暂停”并操纵环境。正如上面的代码所示,我无法弄清楚如何正确分解代码并再次执行它。这让我觉得我应该能够使用一些工具来解析代码并按照我的意愿运行它。 现在我想astpdb 就像你们建议的那样。

    快速浏览表明ast 可以做到这一点,但它似乎有点复杂,所以我必须深入研究文档。如果pdb 可以通过编程方式控制流程,那也很可能是答案。

    更新:

    Sooo,我阅读了更多内容,发现了这个主题:What cool hacks can be done using sys.settrace?

    我考虑过使用sys.settrace(),但似乎不是要走的路。我越来越相信我需要使用ast 模块来获得我想要的精细控制。 FWIW 这是使用settrace() 在函数范围变量内达到峰值的代码:

    import sys
    
    def trace_func(frame,event,arg):
        print "trace locals:"
        for l in frame.f_locals:
            print "\t%s = %s" % (l, frame.f_locals[l])
    
    def dummy(ls):
        for l in ls: pass
    
    sys.settrace(trace_func)
    x = 5
    dummy([1, 2, 3])
    print "whatisthisidonteven-"
    

    输出:

    morten@laptop /tmp $ python t.py 
    trace locals:
        ls = [1, 2, 3]
    whatisthisidonteven-
    trace locals:
        item = <weakref at 0xb78289b4; dead>
        selfref = <weakref at 0xb783d02c; to 'WeakSet' at 0xb783a80c>
    trace locals:
        item = <weakref at 0xb782889c; dead>
        selfref = <weakref at 0xb7828504; to 'WeakSet' at 0xb78268ac>
    

    更新:

    好吧,我似乎已经解决了.. :) 我编写了一个简单的解析器,它在每行代码之间注入一个语句,然后执行代码.. 这个语句是一个函数调用,它捕获并保存本地环境在它的当前状态。

    我正在开发一个 Tkinter 文本编辑器,它有两个窗口,可以完成 Bret Victor 在他的 binarySearch 演示中所做的工作。我快完成了:)

    【讨论】:

    • 是的,完全理解并在这里回复。我想为你试一试。
    • 我还需要做一些工作,但我想我会在周末完成它:) 每当我有东西运行时,我会回击你
    • 我也在做同样的事情,但还没有做到。我做了一个rough proof of concept,它每秒只执行一个完整的代码块。现在我正在尝试构建一个 Eclipse 插件,它将实时编码添加到 PyDev。我刚刚弄清楚如何将add an extra ruler 发送到 Eclipse 编辑器。如果你想一起工作,请告诉我。
    • 我有局部变量赋值和循环出现在标尺中。 Check it out、@Droogans 和 Morten。
    【解决方案5】:

    对于简单的跟踪,我建议您使用pdb。我发现这对于大多数调试/单步执行目的来说是相当合理的。以您为例:

    import pdb
    ...
    xs = []
    pdb.set_trace()
    for x in xrange(10):
        xs.append(x)
    

    现在您的程序将在set_trace() 调用处停止,您可以在代码执行时使用ns 单步执行您的代码。 AFAIK pdb 使用 bdb 作为其后端。

    【讨论】:

    • 我昨天读了一些关于pdb 的文章,但似乎我应该手动进行流量控制。我对此是否正确?我希望能够以编程方式控制流程。一次只单步执行一个语句,同时让我可以在语句之间做一些事情。
    • 与任何交互式调试器一样,您可以设置断点、检查变量或修改它们。
    • Puh -- 我从来没有做过我必须同意的......只是将它用于一般的调试任务。
    【解决方案6】:

    我看到你想出了一些适合你的东西, 但认为值得一提的是'pyscripter'。 http://code.google.com/p/pyscripter/

    我对 python 还很陌生,但我发现它对
    非常有用 只需单击包含我要检查的变量的行,
    然后按 f4 以调试器模式运行它。
    之后,我只需将鼠标悬停在变量上,它就会弹出
    具有变量值的工具提示。

    您也可以使用 f7 单步执行脚本,如下所述:
    http://openbookproject.net/thinkcs/python/english3e/functions.html#flow-of-execution
    (参见“观察执行流程”)

    虽然当我按照示例进行时,它仍然由于某种原因进入了海龟模块。

    【讨论】:

    • 酷,我一定会调查的 :)
    【解决方案7】:

    下载eclipse+pydev并在调试模式下运行...

    【讨论】:

    • 我认为这还不够强大。我想做的不仅仅是看节目状态。例如,我想构建所有可能的程序状态的网格,遍历所有状态并在每个状态下断言一个谓词。 for state in states: assert state.locals["var"] &gt;= 0 什么的
    • 如果您的意思是您一般没见过,请查看the Spin model checker。具有并发性和 IPC 的有限 DSL,可以验证代码上的谓词。你建立你的系统模型并证明它的东西。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-02-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多