2017-02-02 25 views
2

如何在源代碼中查找所有隱式轉換爲布爾值?這包括像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 x is Noneif x == 0等代替if not x)。當然,它必須手動完成,但至少要識別發生隱式轉換的位置會有所幫助。

+0

您知道'x或y'是一種混合型 - 輸入*被評估爲布爾值,但完整表達式的*輸出*將是其中一個輸入(不是布爾值)。 –

回答

0

其實,有一個打字庫,不正是這樣。它適用於python 2和python 3.

請參閱mypy,使用命令--strict-boolean

我接受的答案移動到這一個,即使@AustinHastings大約有一個會如何使用ast做一個非常有用的答案,因爲我想讓人們知道mypy - 這是一個偉大的工具(不太可能是包括Guido在內的100多名貢獻者在任何時候都會放棄。

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 
+0

我希望能夠做到這一點,而不是動態的 - 主要是因爲沒有辦法確保在測試過程中每個強制都會在執行路徑上。另外,沒有辦法裝飾內建類型的'__bool__',這是大多數隱式轉換髮生的地方。 – max

+0

@max我明白。那麼,我會讓我的答案在這裏,因爲它可能是有用的,但肯定這並不能解決你的問題... –

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=[])]) 

禮Bendersky已與AST的工作寫了一個article,他包括一些示例代碼訪問 AST的節點。你會想在你尋找特定結構的地方進行一次訪問。在上面的例子中,你會尋找其中的操作數或者直接當作一個布爾值,或者作爲唯一的操作數到Not()節點處理的If節點下(子)的表達。

尋找每一個可能的案例可能會非常複雜。但我認爲你可以很容易地找到一個或兩個代碼的「簡單」情況(如果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): 
           ^^^^^ 
+0

作爲補充,你應該閱讀[語法](https://docs.python.org/3 /reference/grammar.html)並確定要分離的確切語句。你想要的可能是'test'。 –