Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get a list of classes and functions from a python file without importing it

I have a python file with some classes and functions defined in it:

class A(object):
    def __init__(self, an_arg, a_default_arg=None):
        pass

def doStuff(an_other_arg, an_other_default_arg=None):
    pass

And I want to get a list of all classes and functions in this file. (their names and parameter definitions are enough)

Now, I do know you can do this with __import__(module_descriptor) and inspect, but this is not an option as the file I'm scanning is from an untrusted source.

My first reaction was to try and create a safe environment to import them, but this seems impossible according to other stackoverflow-questions.

like image 373
Pinna_be Avatar asked Jun 22 '17 11:06

Pinna_be


People also ask

How do I list all functions in a Python file?

We can list down all the functions present in a Python module by simply using the dir() method in the Python shell or in the command prompt shell.

How do I print all the functions in a Python module?

To list all functions in a Python module you can use dir(module).

How do I see classes in Python?

To get the class name of an instance in Python: Use the type() function and __name__ to get the type or class of the Object/Instance. Using the combination of the __class__ and __name__ to get the type or class of the Object/Instance.


Video Answer


2 Answers

You can use the ast module to parse the source file, without actually executing any code. Then you can traverse the node tree to get the function and class names/parameters.

import ast

def show_info(functionNode):
    print("Function name:", functionNode.name)
    print("Args:")
    for arg in functionNode.args.args:
        #import pdb; pdb.set_trace()
        print("\tParameter name:", arg.arg)


filename = "untrusted.py"
with open(filename) as file:
    node = ast.parse(file.read())

functions = [n for n in node.body if isinstance(n, ast.FunctionDef)]
classes = [n for n in node.body if isinstance(n, ast.ClassDef)]

for function in functions:
    show_info(function)

for class_ in classes:
    print("Class name:", class_.name)
    methods = [n for n in class_.body if isinstance(n, ast.FunctionDef)]
    for method in methods:
        show_info(method)

Result:

Function name: doStuff
Args:
        Parameter name: an_other_arg
        Parameter name: an_other_default_arg
Class name: A
Function name: __init__
Args:
        Parameter name: self
        Parameter name: an_arg
        Parameter name: a_default_arg
like image 101
Kevin Avatar answered Sep 19 '22 17:09

Kevin


The accepted solution is incomplete. Consider the following file:

def regular_function():
    def nested_function():
        pass

async def async_function():
    pass

The accepted solution will only print:

Function name: regular_function
Args:

To get all functions, we need to make two changes:

  1. Walk the entire AST, rather than just top level nodes
  2. Handle async functions as well as regular functions

Here is the corrected code, for finding functions:

import ast

from pathlib import Path

parsed_ast = ast.parse(Path(__file__).read_text())

functions = [
    node
    for node in ast.walk(parsed_ast)
    if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
]

for function in functions:
    print(f"Function name: {function.name}")
    print(f"Args: {', '.join([arg.arg for arg in function.args.args])}")

Note that this is pushing up against the bounds of what an AST walk should be used for. For anything more complicated, consider using NodeVisitor

like image 44
GBleaney Avatar answered Sep 16 '22 17:09

GBleaney