当我修改对象属性的对象方法返回self
以便链接方法调用时,我真的很喜欢它。例如:
boundingBox.grow(0.05).shift(x=1.3)
而不是
boundingBox.grow(0.05)
boundingBox.shift(x=1.3)
我想搜索旧项目的代码来调整这种模式。如何找到没有return语句的方法?
理想情况下,我想让程序在文件夹上运行。该程序搜索Python文件,查找类,检查其方法并搜索返回语句。如果没有return语句,则输出文件名,类的名称和方法的名称。
答案 0 :(得分:6)
您可以使用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}]}})}
或者我们可以通过从正文中的最后一个arg获取行号来猜测返回的丢失位置:
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")