0

How can I iterate though all of the functions in a specific module from my current place of execution?

Here is some detail on what I've done thus far:

I've created a package called "test_cases" that has a module, "test_cases" which holds dozens of functions. I'd like to create some kind of loop that executes every function in the test_case module.

Below the first line in main() is test_cases.test_cases.tc_1() this is just to test that my script can call on a specific function and it works. Its the "looping" aspect that is troubling me.

Here is the rough idea of my code:

def main():
    # test_cases.test_cases.tc_1()
    for _, test in test_cases.test_cases:
        test

I've read this post but the answer suggests to use for name, val in my_module.__dict__.iteritems(): however iteritems() is not an attribute of __dict__ in python 3.8 according to PEP 469 (assuming I've read that right). I've also read this post but it is still not clear. So I am a little perplexed on what to do.

I'm running Windows 10, PyCharm2020.1.2, Python 3.8

EDIT 1

Here is my new main() function with items() used as the iterator. When debugging, it seems to step through the __name__, __doc__, __package__ etc elements (what are those called?) of my test_case module. It then identifies my function, tc_1 and attempts to execute it but nothing happens. I would expect it to run the print() statement. I've attached my function below for reference. Whats going on here?

def main():
    for name, test in test_cases.test_cases.__dict__.items():
        if callable(test):
            test

# tc_1 is in the test_cases module 
def tc_1():
    print("TEST CASE ONE EXECUTED")

EDIT 2

The final issue was not including the parenthesis after test. Once this was corrected the loop works as expected. Here is the code for reference:

def main():
    for name, test in test_cases.test_cases.__dict__.items():
        if callable(test):
            test()
Tim51
  • 141
  • 3
  • 13

2 Answers2

1

Have you tried using getattr() function? You can combine it with the filtered list of modules as @Reedinator mentioned

Here's an example of how it could be used:

# mymodule.py
def func_A():
    return "Hello this is function A!"


def func_B():
    return "Function B is counting bottles on the wall ..."


def func_C():
    return "Function C ... well let's see ..."

# in python shell
>>> import mymodule
>>> lst = [x for x in dir(mymodule) if not x.startswith('__')]
>>> print(lst)
['func_A', 'func_B', 'func_C']

>>> for f in lst:
...     el = getattr(mymodule, f)
...     el()
... 
'Hello this is function A!'
'Function B is counting bottles on the wall ...'
"Function C ... well let's see ..."
Fadi
  • 32
  • 1
  • 5
  • Also this article may be of help as well: https://tomassetti.me/python-reflection-how-to-list-modules-and-inspect-functions/ – Fadi Jun 25 '20 at 21:06
  • Hi @Fadi, I have not tried the getatt() function. I think I've solve the issue see my EDITs above. What are your thoughts on the current solution? – Tim51 Jun 25 '20 at 21:31
  • I would hesitate in using `__dict__` ... see [this comment](https://stackoverflow.com/a/14361362/10100742). I like the test of `callable(object)`. I was working under the assumption that the callable functions would be those without underscores. – Fadi Jun 25 '20 at 21:47
  • you're right. The `__dict__` caused issues. The problem with using `__dict__` to identify all functions is that it pulls EVERYTHING that is attributed to that module which includes any `imports`. So I've adopted your method of `if x.startswith('tc')` so that after the `__dict__` is used I only execute functions that start with `tc`. Thank you for input! – Tim51 Jun 26 '20 at 01:31
0

In python 3 dictionaries have a .items() method. I believe that .iteritems() is Python 2 specific. A simple test of the methods available in python 3:

>>>test = {}
>>>dir(test)
['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']

EDIT

You need to use the name variable that you are throwing away to sort. If all your tests start with "test" then just run

for name, test in test_cases.test_cases.__dict__.items():
    if name.startswith("test") and callable(test):
        test()

This really comes down to only you will know specifically how to filter the functions out, but you can always add a couple more logical conditions. Just use the actual name of the function to do that.

Reedinationer
  • 5,661
  • 1
  • 12
  • 33
  • Are you suggesting that I somehow use `dir()` to iterate though the functions in the my test_case module? @Reedinationer – Tim51 Jun 25 '20 at 20:50
  • 1
    @Tim51 No. I'm suggesting you use `for name, val in my_module.__dict__.items()` instead of `for name, val in my_module.__dict__.iteritems()`. The `dir()` was to show you that `.iteritems()` is not an available method/attribute of a python 3 dictionary, while `.items()` is – Reedinationer Jun 25 '20 at 20:52
  • Got it. I've done that and it seems to "execute" the callable function. But the function doesn't do what I would expect. I've updated my post with an edit to explain. @Reedinationer – Tim51 Jun 25 '20 at 21:16
  • 1
    @Tim51 It appears your indentation is off, but perhaps you just posted it incorrectly. Where you have `test` is also missing parenthesis, so it's not actually doing anything. You need `test()`. You should be able to verify this with `print(test)` and print(test())`. Without parenthesis you will get `` (or something like that), while with parenthesis you will see printed the return value of your function (`None` unless you have specified one) – Reedinationer Jun 25 '20 at 21:21
  • The indentation was a typo. It has been corrected. The missing " () " was the issue. Thank you for the time and support! @Reedinationer – Tim51 Jun 25 '20 at 21:26
  • @Tim51 Cool! It would be great if you click the check mark by my answer to mark this as resolved. – Reedinationer Jun 25 '20 at 21:28
  • there is a problem with your iteration method. `for name, test in test_cases.test_cases.__dict__.items():` will create list of tuples that contain EVERY method, module import, and variable within `test_cases`. The issue here is I have imports within `test_cases` and because of this the `callable(object)` will filter them thought the `if` statement and then `test()` attempts to execute them. Is there anyway to only extract the specific functions that I need in such a way that would allow for iteration? – Tim51 Jun 26 '20 at 02:18
  • @Tim51 Have you tried printing the `name` variable? You could probably sort by that with another if statement. I included a new example – Reedinationer Jun 26 '20 at 15:32
  • that was the trick. Completely makes sense. I was actually trying to use `if callable(test) and tests.startswith('test')` last night and it was failing. I see where the error was with not using `name` in `name.startswith('test')`. Thank you for the help I very much appreciate it! – Tim51 Jun 26 '20 at 15:55