песочница / запуск кода Python построчно - PullRequest
13 голосов
/ 12 марта 2012

Я бы хотел сделать что-то похожее на то, что делают эти двое:

Принципы изобретения @ 18: 20 , Live ClojureScript Game Editor

Если вы не хотите проверять видео, моя проблема заключается в следующем:

Скажем, у меня был этот код:

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

Я хотел бы создать среду, в которойЯ могу выполнить код, оператор для оператора и наблюдать / отслеживать локальные / глобальные переменные по мере их изменения.Может быть, дать ему список переменных для отслеживания в словарях местных / глобальных.Как пошаговое выполнение кода и сохранение информации о состоянии.

Оптимально, я бы хотел сохранить каждое состояние и связанные с ним данные контекста (локальные / глобальные), чтобы я мог проверить, например, предикаты.

Я хотел бы сделать что-то вроде примера двоичного поиска Брета Виктора Изобретая по принципу @ 18: 20

Имею ли я смысл? Мне сложно объяснить втекст, но видео демонстрируют то, что я хочу попробовать:)

Спасибо за ваше время


Что я пробовал / читал / гуглил:

Мой следующий шаг будетизучать ast и компилировать код и запускать его побитно, но мне действительно нужны некоторые рекомендации ... Должен ли я больше взглянуть на рефлексию и inspect -модуль ??

I 'Мы использовали модуль проверки Spin и раньше, но он использует свой собственный DSL, и я просто хотел бы сделать моделирование на языке реализации, в данном случае python.

Oh и BTW Iзнать о последствиях кода песочницы для безопасности, но я не пытаюсь создать безопасную среду выполнения, я пытаюсь создать очень интерактивную среду, нацеленную, например, на грубую проверку модели или утверждение предиката.

Ответы [ 7 ]

7 голосов
/ 06 сентября 2012

После моего начального успеха с sys.settrace() я закончил тем, что переключился на ast модуль (деревья абстрактного синтаксиса).Я анализирую код, который хочу проанализировать, а затем вставляю новые вызовы после каждого назначения, чтобы сообщить об имени переменной и ее новом значении.Я также вставляю вызовы, чтобы сообщить об итерациях цикла и вызовах функций.Затем я выполняю измененное дерево.

        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, и вы можете увидеть мой рабочий код на GitHub и некоторые примеры того, какон ведет себя в тесте .Вы также можете найти ссылки на демонстрационное видео, учебное пособие и материалы для загрузки с на странице проекта .

4 голосов
/ 16 мая 2012

Обновление: После моего первого успеха с этой техникой я перешел на использование модуля ast, как описано в мой другой ответ .

sys.settrace(), кажется, работает очень хорошо. Я ответил на вопрос о взломах , который вы упомянули, и статью Эндрю Далка , и этот простой пример сработал.

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 голосов
/ 13 марта 2012

Хорошо, ребята, я немного продвинулся.

Скажем, у нас есть такой исходный файл, мы хотим запустить оператор за оператором:

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

Так что это то, чего я хочу. Этот код разбивает источник на исполняемые операторы, и я могу «делать паузу» между операторами и манипулировать средой. Как показывает код выше, я не могу понять, как правильно разбить код и выполнить его снова. Это заставило меня подумать, что я должен иметь возможность использовать какой-то инструмент для разбора кода и запуска его так, как я хочу. Прямо сейчас я думаю ast или pdb, как вы, ребята, предлагаете.

Беглый взгляд показывает, что ast может сделать это, но это кажется немного сложным, поэтому мне придется копаться в документации. Если pdb может управлять потоком программным путем, это тоже может быть ответом.

Обновление:

Ооо, я еще кое-что прочитал и нашел эту тему: Какие крутые хаки можно сделать с помощью sys.settrace?

Я изучил использование sys.settrace(), но, похоже, это не тот путь. Я все больше и больше убеждаюсь в том, что мне нужно использовать модуль ast, чтобы получить настолько хороший контроль, насколько мне хотелось бы. Вот код, который нужно использовать 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>

UPDATE:

Хорошо, похоже, я решил это .. :) Я написал простой парсер, который вставляет оператор между каждой строкой кода, а затем выполняет код. Этот оператор является вызовом функции, которая захватывает и сохраняет локальную среду в своем текущее состояние.

Я работаю над текстовым редактором Tkinter с двумя окнами, которые будут делать то же, что и Брет Виктор в своей двоичной демонстрации поиска. Я почти закончил:)

3 голосов
/ 12 марта 2012

Похоже, вам нужен bdb, библиотека отладчика python. Он встроен, и документы здесь: http://docs.python.org/library/bdb.html

Он не обладает всеми функциями, которые вам нужны, но это разумное место для начала его реализации.

2 голосов
/ 15 марта 2012

Я вижу, что вы придумали что-то, что работает для вас, но подумал, что стоит упомянуть «писатель». http://code.google.com/p/pyscripter/

Я довольно новичок в Python, но я нахожу это очень полезным для
просто щелкните за строкой, в которой есть переменная, которую я хочу проверить,
затем нажмите клавишу f4, чтобы запустить его в режиме отладчика.
После этого я могу просто навести курсор мыши на переменную, и она появляется
всплывающая подсказка со значениями переменной.

Вы также можете пошагово пройти через скрипт с помощью f7, как описано здесь:
http://openbookproject.net/thinkcs/python/english3e/functions.html#flow-of-execution
(см. «Наблюдайте за ходом исполнения в действии»)

Хотя когда я последовал примеру, он почему-то вошел в модуль черепахи.

2 голосов
/ 13 марта 2012

Для простой трассировки я предлагаю вам использовать pdb .Я обнаружил, что это вполне разумно для большинства целей отладки / пошагового выполнения.Например:

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

Теперь ваша программа остановится на вызове set_trace(), и вы можете использовать n или s для пошагового выполнения кода во время его выполнения.AFAIK pdb использует bdb в качестве бэкэнда.

0 голосов
/ 12 марта 2012

скачать eclipse + pydev и запустить его в режиме отладки ...

...