2017-02-02 5 views
2

Как я могу найти все неявные преобразования в boolean в исходном коде? Сюда относятся условные заявления, такие как if x, петли вроде while x, операторы вроде x or y и т. Д .; но не if x == 0 или if len(x) == 0 и т. д. Я не против использовать статический анализатор, IDE или регулярное выражение, или библиотеку python, предназначенную для этой цели. Конечно, будут некоторые ложные срабатывания, когда x на самом деле является логическим; хорошо.Поиск неявного принуждения в boolean в исходном коде

Use case: Я нашел ошибки, вызванные принуждением к boolean. Например, переменная x должна была быть целым числом или None и была неправильно протестирована с if not x, подразумевая if x is None. Я хочу сделать все логические преобразования явными (например, заменяя if not x на if x is None или if x == 0 и т. Д.). Конечно, это нужно было бы сделать вручную, но, по крайней мере, определить места, где происходит неявное преобразование, было бы полезно.

+0

Вы знаете, что 'x или y' является своего рода гибридом - входы * оцениваются * как логические, но * вывод * полного выражения будет одним из входов (а не булевым). –

ответ

0

На самом деле, есть библиотека, типирование, что делает именно это. Он работает как для python 2, так и для python 3.

См. mypy, используйте команду --strict-boolean.

Я двигаюсь принятый ответ на этот, несмотря на то, @AustinHastings имеет очень полезный ответ на вопрос о том, как можно сделать это с помощью ast, потому что я хочу, чтобы люди были в курсе mypy - это отличный инструмент (скорее всего, не будет заброшенный в ближайшее время, со 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=[])]) 

Эли Бендерский написал article по работе с AST, и он включает в себя некоторые примеры кода для посещения Узлы АСТ. Вы хотели бы посетить, где искали конкретные конструкции. В приведенном выше примере вы будете искать (суб) выражения под узлом If, где операнд либо был непосредственно обработан как логический, либо рассматривался как единственный операнд для узла Not().

Поиск любого возможного случая может быть довольно сложным. Но я думаю, что вы можете легко найти «простые» случаи (если x, если не x, если x или y) со страницей или двумя кодами.

EDIT: Вот код, который (я думаю) делает то, что вы хотите.

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'. –

Смежные вопросы