如何确定函数(的源代码)是否包含循环?

时间:2019-01-08 13:28:38

标签: python loops abstract-syntax-tree static-analysis inspect

比方说,我有一堆函数abcde,我想知道它们是否直接使用循环:

def a():
    for i in range(3):
        print(i**2)

def b():
    i = 0
    while i < 3:
        print(i**2)
        i += 1

def c():
    print("\n".join([str(i**2) for i in range(3)]))

def d():
    print("\n".join(["0", "1", "4"]))

def e():
    "for"

我想编写一个函数uses_loop,以便可以期望这些断言通过:

assert uses_loop(a) == True
assert uses_loop(b) == True
assert uses_loop(c) == False
assert uses_loop(d) == False
assert uses_loop(e) == False

(我希望uses_loop(c)返回False,因为c使用列表推导而不是循环。)

我无法修改abcde。因此,我认为有可能为此使用ast并逐步研究从inspect.getsource获得的函数代码。 但是我对其他任何提议都持开放态度,这只是一个想法,如何运作。

这是我自带的ast

def uses_loop(function):
    import ast
    import inspect
    nodes = ast.walk(ast.parse(inspect.getsource(function)))
    for node in nodes:
        print(node.__dict__)

3 个答案:

答案 0 :(得分:7)

您需要检查函数的抽象句法树是否具有作为ast.Forast.Whileast.AsyncFor实例的节点。您可以使用ast.walk()来访问AST的每个节点

import ast
import inspect

def uses_loop(function):
    loop_statements = ast.For, ast.While, ast.AsyncFor

    nodes = ast.walk(ast.parse(inspect.getsource(function)))
    return any(isinstance(node, loop_statements) for node in nodes)

看到documentation for astasync foradded in 3.5

答案 1 :(得分:1)

您快到了!您要做的就是找出如何从身体对象中获取数据。它们都是某些Node类型之后的所有属性。我只是使用getattr(node, 'body', [])来获取子项,并且如果其中任何一个是_ast.For_ast.While的子级,则返回True。

注意:我只是在修改代码。不知道这是否记录在某处并且可以依靠。我想可能是你可以查一下吗? :)

def a():
    for i in range(3):
        print(i**2)

def b():
    i = 0
    while i < 3:
        print(i**2)
        i += 1

def c():
    print("\n".join([str(i**2) for i in range(3)]))

def d():
    print("\n".join(["0", "1", "4"]))

def uses_loop(function):
    import ast
    import _ast
    import inspect
    nodes = ast.walk(ast.parse(inspect.getsource(function)))
    return any(isinstance(node, (_ast.For, _ast.While)) for node in nodes)


print(uses_loop(a))    # True
print(uses_loop(b))    # True
print(uses_loop(c))    # False
print(uses_loop(d))    # False

答案 2 :(得分:0)

如果您只是想检查函数体中是否包含关键字“ for”或“ while”,则可以执行以下操作:

def uses_loop(func_name):
    import inspect
    lines = inspect.getsource(func_name)
    return 'for' in lines or 'while' in lines