Как я могу найти методы Python без операторов возврата?

Мне очень нравится, когда методы объектов, которые изменяют свойство объектов, возвращают self чтобы вы могли цеплять вызовы методов. Например:

 boundingBox.grow(0.05).shift(x=1.3) 

вместо

 boundingBox.grow(0.05) boundingBox.shift(x=1.3) 

Я бы хотел найти код моих старых проектов, чтобы настроить этот шаблон. Как найти методы, которые не имеют оператора return?

В идеале я хочу, чтобы программа запускалась через папку. Программа ищет файлы Python, ищет классы, проверяет их методы и ищет возвращаемые операторы. Если оператор возврата не существует, он выдает имя файла, имя класса и имя метода.

    One Solution collect form web for “Как я могу найти методы Python без операторов возврата?”

    Вы можете получить имена с помощью ast, я буду работать над получением номеров строк:

     import inspect import importlib import ast class FindReturn(ast.NodeVisitor): def __init__(self): self.data = [] def visit_ClassDef(self,node): self.data.append(node.name) self.generic_visit(node) def visit_FunctionDef(self, node): if not any(isinstance(n, ast.Return) for n in node.body): self.data.append(node.name) self.generic_visit(node) mod = "test" mod = importlib.import_module(mod) p = ast.parse(inspect.getsource(mod)) f = FindReturn() f.visit(p) print(f.data) 

    Входные данные:

     class Foo(object): def __init__(self): self.foo = "foo" def meth1(self): self.bar = "bar" def meth2(self): self.foobar = "foobar" def meth3(self): self.returns = "foobar" return self.returns class Bar(object): def __init__(self): self.foo = "foo" def meth1(self): self.bar = "bar" def meth2(self): self.foobar = "foobar" def meth3(self): self.returns = "foobar" return self.returns 

    Вывод:

     ['Foo', '__init__', 'meth1', 'meth2', 'Bar', '__init__', 'meth1', 'meth2'] 

    Имя файла, очевидно, "test.py" здесь.

    Вероятно, это лучший способ группировки данных:

     import inspect import importlib import ast from collections import defaultdict mod = "test" mod = importlib.import_module(mod) p = ast.parse(inspect.getsource(mod)) data = defaultdict(defaultdict) classes = [cls for cls in p.body if isinstance(cls, ast.ClassDef)] for cls in classes: name = "class_{}".format(cls.name) data[mod][name] = {"methods": []} for node in cls.body: if not any(isinstance(n, ast.Return) for n in node.body): if node.name != "__init__": data[mod][name]["methods"].append(node.name) 

    Вывод:

     {<module 'test' from '/home/padraic/test.pyc'>: defaultdict(None, {'class_Foo': {'methods': ['meth1', 'meth2']}, 'class_Bar': {'methods': ['meth1', 'meth2']}})} 

    Чтобы пройти через каталог:

     data = defaultdict(defaultdict) import os path = "/home/padraic/tests" for py in os.listdir(path): with open(os.path.join(path,py)) as f: p = ast.parse(f.read(), "", "exec") classes = [cls for cls in p.body if isinstance(cls, ast.ClassDef)] for cls in classes: name = "class_{}".format(cls.name) data[py][name] = {"methods": []} for node in cls.body: if not any(isinstance(n, ast.Return) for n in node.body): if node.name != "__init__": data[py][name]["methods"].append(node.name) from pprint import pprint as pp pp(dict(data)) {'test.py': defaultdict(None, {'class_Foo': {'methods': ['meth1', 'meth2']}, 'class_Bar': {'methods': ['meth1', 'meth2']}}),'test2.py': defaultdict(None, {'class_Test2': {'methods': ['test1', 'test2']}})} 

    Где test2 содержит:

     class Test2: def test1(self): pass def test2(self): self.f=4 s = self.test_return() i = 3 def test_return(self): return "Test2" 

    Вы можете получить строку до определения метода с помощью node.lineno:

     classes = [cls for cls in p.body if isinstance(cls, ast.ClassDef)] for cls in classes: name = "class_{}".format(cls.name) data[py][name] = {"methods": []} for node in cls.body: if not any(isinstance(n, ast.Return) for n in node.body): if node.name != "__init__": data[py][name]["methods"].append({"meth":node.name,"line":node.lineno}) 

    Вывод:

     {'test.py': defaultdict(None, {'class_Foo': {'methods': [{'meth': 'meth1', 'line': 6}, {'meth': 'meth2', 'line': 9}]}, 'class_Bar': {'methods': [{'meth': 'meth1', 'line': 21}, {'meth': 'meth2', 'line': 24}]}}), 'test2.py': defaultdict(None, {'class_Test2': {'methods': [{'meth': 'test1', 'line': 2}, {'meth': 'test2', 'line': 5}]}})} 

    Или мы можем оценить, где отсутствует возврат, получив номер строки от последнего аргумента в теле:

     data[py][name]["methods"].append({"meth":node.name,"line": node.body[-1].lineno}) 

    Вывод:

     {'test.py': defaultdict(None, {'class_Foo': {'methods': [{'meth': 'meth1', 'line': 7}, {'meth': 'meth2', 'line': 10}]}, 'class_Bar': {'methods': [{'meth': 'meth1', 'line': 22}, {'meth': 'meth2', 'line': 25}]}}), 'test2.py': defaultdict(None, {'class_Test2': {'methods': [{'meth': 'test1', 'line': 3}, {'meth': 'test2', 'line': 8}]}})} 

    Также лучше использовать iglob для игнорирования других файлов:

     import glob for py in glob.iglob(os.path.join(path,"*.py")): with open(os.path.join(path, py)) as f: p = ast.parse(f.read(), "", "exec") 
    Python - лучший язык программирования в мире.