Let's say, I have a bunch of functions a
, b
, c
, d
and e
and I want to find out if they call any method from the random
module:
def a():
pass
def b():
import random
def c():
import random
random.randint(0, 1)
def d():
import random as ra
ra.randint(0, 1)
def e():
from random import randint as ra
ra(0, 1)
I want to write a function uses_module
so I can expect these assertions to pass:
assert uses_module(a) == False
assert uses_module(b) == False
assert uses_module(c) == True
assert uses_module(d) == True
assert uses_module(e) == True
(uses_module(b)
is False
because random
is only imported but never one of its methods called.)
I can't modify a
, b
, c
, d
and e
. So I thought it might be possible to use ast
for this and walk along the function's code which I get from inspect.getsource
. But I'm open to any other proposals, this was only an idea how it could work.
This is as far as I've come with ast
:
def uses_module(function):
import ast
import inspect
nodes = ast.walk(ast.parse(inspect.getsource(function)))
for node in nodes:
print(node.__dict__)
We use the getsource() method of inspect module to get the source code of the function. Returns the text of the source code for an object. The argument may be a module, class, method, function, traceback, frame, or code object. The source code is returned as a single string.
__main__ is the name of the environment where top-level code is run. “Top-level code” is the first user-specified Python module that starts running. It's “top-level” because it imports all other modules that the program needs. Sometimes “top-level code” is called an entry point to the application.
You can use dir(module) to see all available methods/attributes.
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.
This is a work in progress, but perhaps it will spark a better idea. I am using the types of nodes in the AST to attempt to assert that a module is imported and some function it provides is used.
I have added what may be the necessary pieces to determine that this is the case to a checker
defaultdict which can be evaluated for some set of conditions, but I am not using all key value pairs to establish an assertion for your use cases.
def uses_module(function):
"""
(WIP) assert that a function uses a module
"""
import ast
import inspect
nodes = ast.walk(ast.parse(inspect.getsource(function)))
checker = defaultdict(set)
for node in nodes:
if type(node) in [ast.alias, ast.Import, ast.Name, ast.Attribute]:
nd = node.__dict__
if type(node) == ast.alias:
checker['alias'].add(nd.get('name'))
if nd.get('name') and nd.get('asname'):
checker['name'].add(nd.get('name'))
checker['asname'].add(nd.get('asname'))
if nd.get('ctx') and nd.get('attr'):
checker['attr'].add(nd.get('attr'))
if nd.get('id'):
checker['id'].add(hex(id(nd.get('ctx'))))
if nd.get('value') and nd.get('ctx'):
checker['value'].add(hex(id(nd.get('ctx'))))
# print(dict(checker)) for debug
# This check passes your use cases, but probably needs to be expanded
if checker.get('alias') and checker.get('id'):
return True
return False
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With