1

I would like to find all functions that return an explicit tuple. E.g.,

    ...
    return x, y

would be in, but

    ...
    return {"x":x, "y":y}

would be out, so regexp searching for return .*, would return too many false positives.

I suspect that pylint/astroid might do what I want, but I am open to all suggestions.

PS. There are no type annotations - I would rather have something generate them automatically, like in ocaml.

sds
  • 58,617
  • 29
  • 161
  • 278
  • Are you using type annotations? That makes problems like this quite a bit simpler -- and if you're embarking on a big refactoring project like the tag implies and you don't yet have type annotations, my advice would be to get 100% type annotation coverage *first* because it'll make the refactor infinitely easier. – Samwise May 15 '23 at 18:43
  • You can use the `ast` module to parse the Python source code into a tree, then look for `return` statements where the value is a tuple literal – juanpa.arrivillaga May 15 '23 at 18:46
  • @juanpa.arrivillaga Would that work for *return (0,0)* as well as *return 0,0* ? – DarkKnight May 15 '23 at 18:48
  • @DarkKnight no, but I don't think the OP cares about that distinction. – juanpa.arrivillaga May 15 '23 at 18:49
  • @juanpa.arrivillaga I guess it depends on ones understanding of "explicit tuple". Also, how about *return func()* where *func* might itself return a tuple. I suppose that would be an example of an implicit tuple – DarkKnight May 15 '23 at 18:51
  • @DarkKnight in my mind, both `(0,0)` and `0,0` are explicit tuples. the exmaple fo returning some expression which evaluates to a tuple (e.g. `return some_var`) would be "implicit" – juanpa.arrivillaga May 15 '23 at 18:52
  • As an aside, you are not going to find anywhere near the capacity for type-inference in Python as you do in ocaml. At the end, you are going to need to address it manually. – juanpa.arrivillaga May 15 '23 at 18:58

2 Answers2

2

Here's a pylint plugin that does what you want.

from astroid import nodes

from pylint.checkers import BaseChecker


class TupleReturnChecker(BaseChecker):
    msgs = {
        "W5100": (
            "Tuple in return %s",
            "tuple-in-return",
            "Raised when a tuple is returned.",
        )
    }

    def visit_return(self, node: nodes.Return) -> None:
        if isinstance(node.value, nodes.Tuple):
            self.add_message("tuple-in-return", args=node.as_string())

But you don't need to use astroid/pylint for that simple case, an ast.NodeVisitor would looks pretty much the same.

import ast

class TupleDetector(ast.NodeVisitor):
   def visit_Return(self, node: ast.Return) -> None:
       if isinstance(node.value, ast.Tuple):
            print(f"{node} is a tuple")

detector = TupleDetector()
with open(code_path) as f:
    detector.visit(ast.parse(f.read()))

pylint would be useful if you have something possibly returning a tuple and you need inference.

Pierre.Sassoulas
  • 3,733
  • 3
  • 33
  • 48
1

You could probably write something quick-and-dirty using the built-in ast module. I'm going to leave the details of actually traversing through the source code out, but the following snippet should demonstrate mostly what you need to know to get there:

In [1]: import ast

In [2]: tree = ast.parse("""
   ...: def foo():
   ...:     print('hi')
   ...:     return x,y
   ...: """)

In [3]:

In [3]: tree.body
Out[3]: [<ast.FunctionDef at 0x10640c310>]

In [4]: func_def = tree.body[0]

In [5]: func_def.body
Out[5]: [<ast.Expr at 0x1064c36d0>, <ast.Return at 0x1064c3e50>]

In [6]: return_stmt = func_def.body[-1]

In [7]: return_stmt
Out[7]: <ast.Return at 0x1064c3e50>

In [8]: return_stmt.value
Out[8]: <ast.Tuple at 0x1064c35e0>

What you need to do, then, parse your source code, recurse through the resulting tree looking for function definition nodes. In the body of those nodes, search for a return node, and check .value for type ast.Tuple (the ctx in a return will always be ast.Load so I don't think you have to worry about that). Note, when you are in a function definition node, you can still find other function definitions.

juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172