如何在没有 return 语句的情况下找到 Python 方法? [英] How can I find Python methods without return statements?

查看:32
本文介绍了如何在没有 return 语句的情况下找到 Python 方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当对象的方法修改对象属性时,我真的很喜欢它返回 self 以便您可以链接方法调用.例如:

I really like it when methods of objects, which modify the objects property, return self so that you can chain method calls. For example:

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

代替

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

我想搜索我旧项目的代码来调整这种模式.如何找到没有 return 语句的方法?

I would like to search the code of my old projects to adjust this pattern. How can I find methods which don't have a return statement?

理想情况下,我想让程序在文件夹上运行.该程序搜索 Python 文件、查找类、检查它们的方法并搜索返回语句.如果没有 return 语句,则输出文件名、类名和方法名.

Ideally, I would like to let a program run over a folder. The program searches Python files, looks for classes, examines their methods and searches return statements. If no return statement is there, it outputs the filename, the name of the class and the name of the method.

推荐答案

你可以用 ast 获取名称,我会努力获取行号:

You can get the names with ast, I will work on getting the line numbers:

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".

这可能是一种更好的数据分组方式:

This is probably a nicer way to group the data:

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 包含:

Where test2 contains:

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 获取方法定义之前的行:

You can get the line before the method definition with 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}]}})}

或者我们可以通过获取 body 中最后一个 arg 的行号来估计缺少 return 的位置:

Or we can guesstimate where the return is missing by getting the line number from the last arg in the body:

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 忽略其他文件也可能更好:

It might also be better to use iglob to ignore other files:

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

这篇关于如何在没有 return 语句的情况下找到 Python 方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆