Почему мой код на python работает так, как ожидается в отладчике, а не иначе? - PullRequest
0 голосов
/ 14 декабря 2018

Я написал парсер в python3.6;Я максимально упростил его, продолжая создавать ошибку:

def tokenize(expr):
    for i in expr:
        try:
            yield int(i)
        except ValueError:
            yield i


def push_on_stream(obj, stream):
    yield obj
    yield from stream


class OpenBracket:
    "just a token value, could have used Ellipsis"
    pass


def parse_toks(tokstream):
    result = []
    leading_brak = False
    for tok in tokstream:
        if tok == OpenBracket:
            leading_brak = True
        elif tok == '(':
            result.append(parse_toks(
                push_on_stream(OpenBracket, tokstream)))
        elif tok == ')':
            if not leading_brak:
                raise SyntaxError("Very bad ')'.")
            break
        else:
            result.append(tok)
    return sum(result)


def test(expr="12(34)21"):
    tokens = tokenize(expr)
    print( parse_toks(tokens) )
    print(list(tokens))

test()

Этот пример тривиален;эффект должен состоять в добавлении всех цифр в строке, включая цифры в скобках.

Функция tokenize () возвращает токены, а функция parse_tok () анализирует поток токенов.Если он сталкивается с открытыми скобками, он повторяется (помещая OpenBracket в поток токенов), что должно приводить к обработке цифр в скобках как отдельного выражения, их синтаксическому анализу и добавлению результата к результат стек.

Когда я анализирую код, например, в выражении "1 (2) 3", он сразу заканчивается после закрывающей скобки, возвращая 3 иНа самом деле поток токенов, похоже, закончился.

Когда я запускаю его, используя pdb, и устанавливаю точки останова внутри цикла в parse_tok, я могу осторожно выполнить шаг, когда он обрабатывает ')', и программа корректно возвращает 6 .

Я думаю, что ошибка связана с выходом из потока токенов в push_on_stream ().

Это ошибка в интерпретаторе?Если да, то есть ли хороший обходной путь?

Я написал его для python-3.6, но я также протестировал его на python-3.7 на другой машине с тем же результатом.

Ответы [ 3 ]

0 голосов
/ 14 декабря 2018

Гипотеза

Когда оператор break выходит из цикла, возникает исключение GeneratorExit, которое распространяется через генераторы.pdb изменяет, как это распространяется, и это именно та тонкая ошибка, которую я ожидал бы вызвать, заставляя его не исчерпать генератор, который push_on_stream yield исходит от.

Test

Если мы изменим push_on_stream с:

def push_on_stream(obj, stream):
    yield obj
    yield from stream

на:

def push_on_stream(obj, stream):
    yield obj
    stream = iter(stream)
    while True:
        yield next(stream)

, то это повлияет на него достаточно, чтобы гарантировать правильное поведение в обоих случаях.

Результат

Исправлена ​​ошибка!

Объяснение

Лучше предоставлено ответом user2357112 . В основном , yield from не работает так, как вы думаете;когда генератор выходит из-за оператора break, yield from заставляет генератор, который вы перебираете, помечать себя как исчерпанный.(pdb прерывает это, потому что это немного глючит.) Это приводит к тому, что ваш синтаксический анализатор завершается в первый ), потому что основной итератор останавливается, когда выполняется первый оператор break.

0 голосов
/ 14 декабря 2018

Ваш push_on_stream не совсем работает так, как вы думаете.

Видите, когда генератор push_on_stream возвращается, Python вызывает close для генератора, который выдает GeneratorExit в генератор, чтобы убедиться, что все finally блоки и __exit__ методы работают.Поскольку push_on_stream использует yield from в базовом генераторе, если push_on_stream приостановлено в yield from, это приводит к GeneratorExit в базовом tokenize генераторе .

Это немедленно прекращает поток токенов.В pdb что-то привело к тому, что генератор push_on_stream не был собран, что предотвратило этот эффект.

0 голосов
/ 14 декабря 2018

Проблема именно там, где вы ее описали:

    elif tok == ')':
        if not leading_brak:
            raise SyntaxError("Very bad ')'.")
        break

Как только вы нажмете правую скобку, вы прервете цикл очистки, либо с исключением, либо с явным разрывом.Просто удалите break.Какие функции вы ожидали здесь?

Тестовый код:

test("1(2)3")
test()
test("1(2(4)8)5")

Выходные значения:

6
[]
13
[]
20
[]
...