在源代码

时间:2017-02-02 00:39:47

标签: python python-3.x static-analysis

如何在源代码中找到所有隐式转换为布尔值?这包括if x之类的条件语句,while x之类的循环,x or y之类的运算符等等。但不是if x == 0if len(x) == 0等。我不介意使用静态分析器,IDE,正则表达式或为此目的设计的python库。当x实际上是布尔值时,会有一些误报;没关系。

用例:我发现强制导致的布尔错误。例如,变量x应该是一个整数或None,并且使用if not x暗示if x is None进行了错误的测试。我想明确所有布尔转换(例如,将if not x替换为if x is Noneif x == 0等。当然,它必须手动完成,但至少要确定隐式转换发生的位置会有所帮助。

3 个答案:

答案 0 :(得分:2)

我建议您查看标准ast模块。这是一些简单的代码:

import ast
source = '''
x=1
if not x:
    print('not x')
'''

tree = ast.parse(source)
print(ast.dump(tree))

这是输出:

$ python test.py
Module(body=[Assign(targets=[Name(id='x', ctx=Store())], value=Num(n=1)), If(test=UnaryOp(op=Not(), operand=Name(id='x', ctx=Load())), body=[Expr(value=Call(func=Name(id='print', ctx=Load()), args=[Str(s='not x')], keywords=[]))], orelse=[])])

Eli Bendersky写了一篇关于使用AST的article,他包含了一些示例代码,用于访问 AST的节点。您可能希望在寻找特定结构的地方进行访问。在上面的示例中,您将在If节点下查找(子)表达式,其中操作数直接被视为布尔值,或被视为Not()节点的唯一操作数。 / p>

查找每个可能的案例可能非常复杂。但我认为你可以很容易地找到一个或两个代码的“简单”情况(如果不是x,如果不是x,如果是x或y)。

编辑:以下是一些代码(我认为)可以满足您的需求。

import ast
source = '''#Line 1
x=1
y=2

if not x:
    print('not x')

if y is None:
    print('y is none')


while y or not x or (x < 1 and not y and x < 10):
    print('x < 10')
    x += 1

'''

tree = ast.parse(source)

class FindNameAsBoolean(ast.NodeVisitor):
    def __init__(self, lines):
        self.source_lines = lines

    def report_find(self, kind, locn, size=3):
        print("\nFound %s at %s" % (kind, locn))
        print(self.source_lines[locn[0]-1])
        print(' ' * locn[1], '^' * size, sep='')

    def visit_UnaryOp(self, node):
        if isinstance(node.op, ast.Not) and isinstance(node.operand, ast.Name):
            self.report_find('NOT-NAME', (node.lineno, node.col_offset), size=4 + len(node.operand.id))
        self.generic_visit(node)

    def visit_BoolOp(self, node):
        opname = type(node.op).__name__.upper()
        for kid in node.values:
            if isinstance(kid, ast.Name):
                self.report_find('%s-NAME' % opname, (node.lineno, node.col_offset), size=len(kid.id))

        self.generic_visit(node)

class FindTests(ast.NodeVisitor):
    def __init__(self, lines):
        self.source_lines = lines

    def _fnab(self, node):
        cond = node.test
        FindNameAsBoolean(self.source_lines).visit(cond)

    def visit_If(self, node):
        self._fnab(node)
        self.generic_visit(node)

    def visit_While(self, node):
        self._fnab(node)
        self.generic_visit(node)

FindTests(source.splitlines()).visit(tree)

这是输出:

$ python test.py

Found NOT-NAME at (5, 3)
if not x:
   ^^^^^

Found OR-NAME at (12, 6)
while y or not x or (x < 1 and not y and x < 10):
      ^

Found NOT-NAME at (12, 11)
while y or not x or (x < 1 and not y and x < 10):
           ^^^^^

Found NOT-NAME at (12, 31)
while y or not x or (x < 1 and not y and x < 10):
                               ^^^^^

答案 1 :(得分:0)

我的第一个想法是装饰内置的bool函数,但出于某种原因,这不适用于Python 3.4。

因此,当我知道可能使用的全套类时,我提出了一个解决方案:基本上装饰每个类的__bool__方法。

def bool_highlighter(f):
    def _f(*args, **kwargs):
        print("Coercion to boolean")
        return f(*args, **kwargs)
    return _f

for c in classes:
    try:
        c.__bool__ = bool_highlighter(c.__bool__)
    except AttributeError:
        pass

我只是假设classes是一个包含目标类的迭代。你可以动态地填充它。

如果在启动时执行此代码,则每个布尔强制都将打印"Coercion to boolean"

只是一个简短的测试:

>>> class Foo:
...     def __init__(self, v):
...         self.v = v
...
...     def __bool__(self):
...         return self.v == 12
...
>>> foo = Foo(15)
>>> if not foo:
...     print("hello")
...
Coercion to boolean
hello

答案 2 :(得分:0)

实际上,有一个打字库正是这样做的。它适用于python 2和python 3。

请参阅mypy,使用命令--strict-boolean

我将接受的答案移到这个,即使@AustinHastings有一个非常有用的答案,关于如何使用ast来做,因为我希望人们知道mypy - 这是一个伟大的工具(不太可能很快被放弃,有100多个贡献者,包括Guido)。