我写了一个名为buildRegex
的方法,给定一个名称(类型为str
),返回一个regex
对象,在{{1}中找到from ... import ... name
语句模块。
例如,以下是Python
的预期行为:
buildRegex
到目前为止我所做的一切都适用于上面的所有例子(以及更多):
>>> regObj = buildRegex('foo')
>>> regObj.search('from a import fool') is None
True
>>> regObj.search('from a import foo') is not None
True
>>> regObj.search('from a.b.c import foo as food') is None
True
>>> regObj.search('from a.b.c import fool, bar as foo') is not None
True
def buildRegex(name):
singleImportedName = r'(\b{0}\b(?!\s+as\s+))'.format(name)
importStatement = r'from\s+(\w+(\.\w+)*)\s+import\s+([^#\n]*)(?={0})'.format(singleImportedName )
return re.compile(importStatement)
假设搜索到的模块没有buildRegex
,这是正常的。
我的问题是,在查找导入的名称SyntaxError
时,我还需要知道它是否是其他名称的别名。即如果模块有语句:
foo
我想知道from a.b.c import bar as foo
是什么样的别名,在这种情况下,是foo
。目前,由于正则表达式中的bar
,这是不可能的。所以,最后我的问题:
如何重构正则表达式以便不丢失此信息,即,如果给定名称是别名,则其别名的名称位于asserted lookaheads
groups之一?
答案 0 :(得分:4)
我建议不要编写复杂的正则表达式来解析导入,而是实际使用ast.parse
将源代码解析为抽象语法树,并从那里找到名称。因为ast.parse
可以保证正确解析Python。类似的东西:
import ast
class ImportFinder(ast.NodeVisitor):
def __init__(self):
self.imports = []
def visit_Import(self, node):
names = []
for i in node.names:
names.append((i.name, i.asname))
self.imports.append(['import', names])
def visit_ImportFrom(self, node):
module = node.module
level = node.level # how many dots
names = []
for i in node.names:
names.append((i.name, i.asname))
self.imports.append(('from', level, module, names))
def parse_imports(source):
tree = ast.parse(source)
finder = ImportFinder()
finder.visit(tree)
return finder.imports
使用示例:
import pprint
pprint.pprint(parse_imports('''
from foo import bar, baz, frob
from .. import bar as spam, baz as ham, frob
import bar.baz
import bar.foo as baf
'''))
打印出来:
[('from', 0, 'foo', [('bar', None), ('baz', None), ('frob', None)]),
('from', 2, None, [('bar', 'spam'), ('baz', 'ham'), ('frob', None)]),
['import', [('bar.baz', None)]],
['import', [('bar.foo', 'baf')]]]
from
行上的整数表示模块名称前的.
数。
答案 1 :(得分:2)
import inspect
import importlib
import ast
class Imports(ast.NodeVisitor):
def visit_Import(self, node):
print("In Import")
for imp in node.names:
if imp.asname is not None:
print("module name = {}, alias = {}".format(imp.name, imp.asname))
else:
print("module name = {}".format(imp.name))
print()
def visit_ImportFrom(self, node):
print("In ImportFrom")
for imp in node.names:
if imp.asname is not None:
print("module = {}\nname = {}\nalias = {}\nlevel = {}\n".
format(node.module, imp.name, imp.asname, node.level))
else:
print("module = {}\nname = {}\nlevel = {}\n".
format(node.module, imp.name, node.level))
print()
mod = "temp_test"
mod = importlib.import_module(mod)
p = ast.parse(inspect.getsource(mod))
Imports().visit(p)
输入:
from bisect import bisect_left as bs
import datetime
import time
import numpy as np
def foo():
from re import findall
class Foo():
def test(self):
from re import compile as cp, finditer as ft
输出:
In ImportFrom
module = bisect
name = bisect_left
alias = bs
level = 0
In Import
module name = datetime
In Import
module name = time
In Import
module name = numpy, alias = np
In ImportFrom
module = re
name = findall
level = 0
In ImportFrom
module = re
name = compile
alias = cp
level = 0
module = re
name = finditer
alias = ft
level = 0
类导入(名称)
导入声明。 names是别名节点列表。
类ImportFrom(模块,名称,级别)
表示来自x import y。 module是'from'名称的原始字符串,没有任何前导点,或者对于诸如from之类的语句是None。导入foo。 level是一个保持相对导入级别的整数(0表示绝对导入)。
对我来说,greentreesnakes文档至少可以更好地解释所有节点的工作以及如何使用ast模块而不是实际的ast文档本身。
您也可以使用直接传递模块或打开py文件并将内容传递给ast.parse:
with open("temp_test.py") as f:
p = ast.parse(f.read(), filename="<ast>", mode="exec")
Imports().visit(p)
传递模块:
import temp_test
p = ast.parse(inspect.getsource(temp_test))
Imports().visit(p)