2015-05-12 4 views
6

Naprawdę podoba mi się, gdy metody obiektów, które modyfikują właściwość obiektów, zwracają self, dzięki czemu można łączyć wywołania metod. Na przykład:Jak mogę znaleźć metody Pythona bez instrukcji return?

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

zamiast

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

Chciałbym poszukać kodu z moich starych projektów, aby dostosować ten wzór. Jak znaleźć metody, które nie mają instrukcji return?

Idealnie, chciałbym pozwolić, aby program działał na folderze. Program przeszukuje pliki Pythona, szuka klas, analizuje ich metody i wyszukuje zwrotne instrukcje. Jeśli nie ma instrukcji return, wypisuje nazwę pliku, nazwę klasy i nazwę metody.

+0

Wcięcie oparte blok struktura Python sprawia, że ​​bardzo łatwo napisać taki program samodzielnie. Po prostu poszukaj 'def (...):' i sprawdź ostatni wiersz następnego bloku, aby sprawdzić, czy ma on 'return'. –

+0

'return' nie musi być w ostatnim wierszu. Wiem, że prawdopodobnie mógłbym napisać program, który działałby dobrze dla (większości) mojego kodu.Wolałbym jednak rozwiązanie, które jest testowane/używane/utrzymywane przez innych. –

+1

Przypisz wywołanie każdej metody i sprawdź, czy jest to "Brak". –

Odpowiedz

6

można uzyskać nazwy z ast, będę pracować na uzyskanie numerów linia:

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) 

Wejście:

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 

wyjściowa:

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

nazwa_pliku jest oczywiście "test.py" tutaj.

To chyba ładniejszy drogę do grupy danych:

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) 

wyjściowa:

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

Aby przejść do katalogu:

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']}})} 

Gdzie test2 zawiera:

class Test2: 
    def test1(self): 
     pass 

    def test2(self): 
     self.f=4 
     s = self.test_return() 
     i = 3 

    def test_return(self): 
     return "Test2" 

można uzyskać linię przed definicji metody z 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}) 

wyjściowa:

{'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}]}})} 

Albo możemy guesstimate gdzie zwrot brakuje dostając numer linii od ostatniego Arg w organizmie:

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

wyjściowa:

{'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}]}})} 

To może być też lepiej używać iglob ignorować inne pliki:

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

Numer linii to po prostu 'node.lineno' dla węzłów' FunctionDef'. – tzaman

+0

@tzaman, tak, okrzyki. Tak naprawdę chodzi mi o brakujące zwroty, ale nie jestem pewien, jak łatwo to będzie. –

+0

Hmm, być może 'max (n.lineno dla n w ast.walk (węzeł))'? – tzaman