如何在源代码中找到所有隐式转换为布尔值?这包括if x
之类的条件语句,while x
之类的循环,x or y
之类的运算符等等。但不是if x == 0
或if len(x) == 0
等。我不介意使用静态分析器,IDE,正则表达式或为此目的设计的python库。当x
实际上是布尔值时,会有一些误报;没关系。
用例:我发现强制导致的布尔错误。例如,变量x
应该是一个整数或None
,并且使用if not x
暗示if x is None
进行了错误的测试。我想明确所有布尔转换(例如,将if not x
替换为if x is None
或if x == 0
等。当然,它必须手动完成,但至少要确定隐式转换发生的位置会有所帮助。
答案 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)。