我有一个多行文本字符串,我想逐行解析一部分python代码,这样我就有一个字符串列表,每个项目代表自己的python语句。
不幸的是,我刚刚构建了整个文本的AST,因为部分文本不包含有效的Python语法。每个python语句也可以跨越多行。但是,对于每个有效的语句,我都知道它从哪一行开始。但是,我无法区分无效语法和前一行的延续。
例如我可能有这样的东西(我将添加一个注释,表示我知道哪些行开始有效的语法,哪些行是无效语法或前一语句的延续)
foo = bar() # valid-start
this = ( # valid-start
'perfectly valid syntax' # unknown
) # unknown
44x=but-this-is-bad-syntax # unknown
这里所需的输出是元组列表,第一项表示语句是有效的python还是垃圾,第二项是与该语句对应的文本。
[
('PY', 'foo = bar()'),
('PY', """this = (
'perfectly valid syntax'
)"""),
('JUNK', '44x=but-this-is-bad-syntax')
]
我考虑过的一个解决方案是检查括号是否平衡,但是当涉及到字符串时这变得很棘手(我也不相信自己在所有情况下都适用)。
foo = bar() # valid-start
this = ''' # valid-start
this is still () # unknown
perfectly valid ))) # unknown
''' # unknown
z = ''' # unknown
even though this is valid # unknown
syntax, I don't want this # unknown
line grouped''' # unknown
44x=but-this-is-bad-syntax # unknown
a = 1 # valid-start
应该产生类似这样的输出:
[
('PY', 'foo = bar()'),
('PY', """this = '''
this is still ()
perfectly valid )))
'''"""),
('JUNK', "z = '''"),
('JUNK', "even though this is valid"),
('JUNK', "syntax, I don't want this"),
('JUNK', "line grouped'''"),
('JUNK', "line grouped"),
('JUNK', "44x=but-this-is-bad-syntax"),
('PY', "a = 1"),
]
请注意,在上一个示例中,以z = '''
开头的行标记为未知。尽管继续使用它仍然会产生有效的语法,但是我希望在语句成为有效语法后停止解析以this = '''
开头的语句,(即不包括z = '''
)
有没有人知道如何做到这一点?
一个pyparsing解决方案,只需检查平衡括号,同时考虑到字符串就足够了吗?我的想法是我会定义一个接受平衡括号/方括号/花括号的语法,其中嵌套的主体可以是任何字符序列或字符串(可能包含parens,但那些不会计入平衡)。然后,我将使用此语法解析行,直到重构的行与原始行完全相等。
有没有人看到上一种方法存在问题/是否有人有更简单的方法来执行此操作而不涉及对pyparsing的依赖?
根据@rici的回答,我想出了一个可以获取行列表的函数,如果行形成一个完整的语句则返回True,否则返回False。
import tokenize
from six.moves import cStringIO as StringIO
def is_complete_statement(lines):
"""
Checks if the lines form a complete python statment.
"""
try:
stream = StringIO()
stream.write('\n'.join(lines))
stream.seek(0)
for t in tokenize.generate_tokens(stream.readline):
pass
except tokenize.TokenError as ex:
message = ex.args[0]
if message.startswith('EOF in multi-line'):
return False
raise
else:
return True
答案 0 :(得分:2)
标准Python库包括标记化和解析Python输入的模块。即使您的用例不适合内置的Python解析器(模块AST
),您也可能会发现tokenize
模块很有用。 (例如,它正确地标记了字符串文字。)
这是Python 2.7中的简单演示:
$ cat tokenize.py
from sys import stdin
from tokenize import generate_tokens
from token import tok_name
for t in generate_tokens(stdin.readline):
print (tok_name[t[0]], t[1])
$ python tokenize.py <<"EOF"
> foo = bar()
> this = '''
> this is still ()
> perfectly valid )))
> '''
> if not True:
> print "false"
> 44x=this-is-bad-syntax but it can be tokenized
> a = 1
> EOF
('NAME', 'foo')
('OP', '=')
('NAME', 'bar')
('OP', '(')
('OP', ')')
('NEWLINE', '\n')
('NAME', 'this')
('OP', '=')
('STRING', "'''\n this is still ()\n perfectly valid )))\n'''")
('NEWLINE', '\n')
('NAME', 'if')
('NAME', 'not')
('NAME', 'True')
('OP', ':')
('NEWLINE', '\n')
('INDENT', ' ')
('NAME', 'print')
('STRING', '"false"')
('NEWLINE', '\n')
('DEDENT', '')
('NUMBER', '44')
('NAME', 'x')
('OP', '=')
('NAME', 'this')
('OP', '-')
('NAME', 'is')
('OP', '-')
('NAME', 'bad')
('OP', '-')
('NAME', 'syntax')
('NAME', 'but')
('NAME', 'it')
('NAME', 'can')
('NAME', 'be')
('NAME', 'tokenized')
('NEWLINE', '\n')
('NAME', 'a')
('OP', '=')
('NUMBER', '1')
('NEWLINE', '\n')
('ENDMARKER', '')