Как определить, является ли синтаксически допустимой одна строка питона?

Это очень похоже на это:

Как узнать, содержит ли строка действительный код Python

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

Формально мы говорим, что строка python "синтаксически корректна", если существует синтаксически действительная программа python, которая использует эту конкретную строку.

Например, я хотел бы идентифицировать их как синтаксически допустимые строки:

for i in range(10):

x = 1

Поскольку эти строки можно использовать в некоторых синтаксически корректных программах python.

Я хотел бы идентифицировать эти строки как синтаксически недействительные строки:

for j in range(10 in range(10(

x =++-+ 1+-

Поскольку никакие синтаксически правильные программы на питоне никогда не могли использовать эти строки

Проверка не должна быть слишком строгой, она просто должна быть достаточно хорошей, чтобы отфильтровать явно ложные утверждения (как показано выше). Конечно, строка указана как строка.

Ответ 1

Используется codeop.compile_command, чтобы попытаться скомпилировать код. Это та же самая логика, что code модуль определяет, нужно ли запрашивать другую строку или сразу сбой с синтаксической ошибкой.

import codeop
def is_valid_code(line):
    try:
        codeop.compile_command(line)
    except SyntaxError:
        return False
    else:
        return True

Его можно использовать следующим образом:

>>> is_valid_code('for i in range(10):')
True
>>> is_valid_code('')
True
>>> is_valid_code('x = 1')
True
>>> is_valid_code('for j in range(10 in range(10(')
True
>>> is_valid_code('x = ++-+ 1+-')
False

Я уверен, что в этот момент вы говорите: "Что дает? for j in range(10 in range(10( должно быть недействительным!" Проблема с этой строкой заключается в том, что 10() технически синтаксически действителен, по крайней мере, согласно интерпретатору Python. В REPL вы получите следующее:

>>> 10()
Traceback (most recent call last):
  File "<pyshell#22>", line 1, in <module>
    10()
TypeError: 'int' object is not callable

Обратите внимание, что это TypeError, а не SyntaxError. ast.parse говорит, что он действителен и просто рассматривает его как вызов с функцией ast.Num.

Эти вещи не могут быть легко пойманы до тех пор, пока они не начнут работать. Если бы какой-то монстр смог изменить значение кэшированного значения 10 (что было бы технически возможно), вы могли бы сделать 10(). Это все еще допускается синтаксисом.

Как насчет несбалансированных круглых скобок? Это соответствует той же схеме, что и for i in range(10):. Эта строка недействительна сама по себе, но может быть первой строкой в ​​многострочном выражении. Например, см. Следующее:

>>> is_valid_code('if x ==')
False
>>> is_valid_code('if (x ==')
True

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

if (x ==
    3):
    print('x is 3!')

и выражение будет полным. Фактически codeop.compile_command различает эти различные ситуации, возвращая объект кода, если он является допустимой автономной линией, None, если строка, как ожидается, продолжит полное выражение и бросание SyntaxError на недопустимую строку.

Однако вы также можете столкнуться с гораздо более сложной проблемой, чем изначально заявлено. Например, рассмотрим строку ). Если это начало модуля или предыдущая строка {, то это недействительно. Однако, если предыдущая строка (1,2,, она полностью действительна.

Решение, данное здесь, будет работать, если вы работаете только вперед и добавляете предыдущие строки в качестве контекста, что является code модулем выполняет для интерактивного сеанса. Создание чего-то, что всегда может точно определить, может ли существовать одна строка в файле Python без учета окружающих линий, будет чрезвычайно сложно, так как грамматика Python взаимодействует с новыми линиями нетривиальными способами. Этот ответ отвечает тем, может ли данная строка быть в начале модуля и продолжать свою работу до следующей строки без сбоев.

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

Ответ 2

Я просто предлагаю, не уверен, что собираюсь работать... Но может быть что-то с exec и try-except?

code_line += "\n" + ("\t" if code_line[-1] == ":" else "") + "pass"
try:
    exec code_line
except SyntaxError:
    print "Oops! Wrong syntax..."
except:
    print "Syntax all right"
else:
    print "Syntax all right"

Простые строки должны вызывать соответствующий ответ