2015-05-12 11 views
6

Mi piace molto quando i metodi degli oggetti, che modificano la proprietà dell'oggetto, restituiscono self in modo da poter concatenare le chiamate ai metodi. Per esempio:Come posso trovare i metodi Python senza istruzioni di ritorno?

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

invece di

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

Vorrei cercare il codice dei miei vecchi progetti per regolare questo modello. Come posso trovare metodi che non hanno una dichiarazione di reso?

Idealmente, mi piacerebbe far passare un programma su una cartella. Il programma cerca i file Python, cerca le classi, esamina i loro metodi e cerca le dichiarazioni di ritorno. Se non ci sono dichiarazioni di ritorno, restituisce il nome file, il nome della classe e il nome del metodo.

+0

La struttura a blocchi rientro a base di Python rende abbastanza facile da scrivere un programma del genere da soli. Basta cercare 'def (...):' e controllare l'ultima riga del seguente blocco per vedere se ha un 'return'. –

+0

'return' non deve essere nell'ultima riga. So che probabilmente potrei scrivere un programma che funzionerebbe bene per (la maggior parte) il mio codice.Ma preferirei una soluzione che è testata/usata/gestita da altri. –

+1

Assegnare la chiamata di ciascun metodo e verificare se è "Nessuno". –

risposta

6

È possibile ottenere i nomi con ast, lavorerò su come ottenere i numeri di riga:

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) 

ingresso:

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 

uscita:

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

Il nome del file è ovviamente "test.py" qui.

Questo è probabilmente un modo migliore per raggruppare i dati:

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) 

uscita:

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

passare attraverso una directory:

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

Dove test2 contiene:

class Test2: 
    def test1(self): 
     pass 

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

    def test_return(self): 
     return "Test2" 

È possibile ottenere la linea prima della definizione del metodo con 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}) 

uscita:

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

Oppure possiamo stimarne il cui rendimento non è presente da ottenere il numero di riga dell'ultima arg nel corpo:

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

uscita:

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

Potrebbe anche essere meglio utilizzare iglob per ignorare altri file:

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

Il numero di riga è solo 'node.lineno' per i nodi' FunctionDef'. – tzaman

+0

@ tzaman, sì, evviva. In realtà intendevo i ritorni mancanti, ma non sono sicuro di quanto sarà facile. –

+0

Hmm, forse 'max (n.lineno per n in ast.walk (nodo))'? – tzaman

Problemi correlati