Python error when creating classes dynamically

84 Views Asked by At

Let's take a project with this folder structure:

> eval
  > a
    > __init__.py
    > a.py
  > b
    > __init__.py
    > b.py
  __init__.py
__init__.py
m.py

Content of __init__.py in a folder:

from eval.a.a import A

Content of a.py:

class A:
    def __init__(self) -> None:
        pass

    def __repr__(self) -> str:
        return "Class A"

    def __str__(self) -> str:
        return "Class A"

    def exec(self):
        a = eval('A()');
        print(a)

        b = eval('B()');
        print(b)

Content of __init__.py in b folder:

from eval.b.b import B

Content of b.py:

class B:
    def __init__(self) -> None:
        pass

    def __repr__(self) -> str:
        return "Class B"

    def __str__(self) -> str:
        return "Class B "

    def exec(self):
        a = eval('A()');
        print(a)

        b = eval('B()');
        print(b)

Content of __init__.py in eval folder:

from eval.a.a import A
from eval.b.b import B

__init__.py outside eval folder has nothing inside.

Content of m.py:

from eval.a import A
from eval.b import B

a = eval('A()');
print(a)

b = eval('B()');
print(b)

Until now it works, creating my A() and B() objects, printing:

Class A
Class B

But if I change m.py to:

from eval.a import A
from eval.b import B

a = eval('A()');
print(a)

b = eval('B()');
print(b)

print('Running A.exec() now:')
a.exec()

print('Running B.exec() now:')
b.exec()

Then I will get:

Class A
Class B 
Running A.exec() now:
Class A
Traceback (most recent call last):
  File "c:\dev\test\python\teste_eval\m.py", line 19, in <module>
    a.exec()
  File "c:\dev\test\python\teste_eval\eval\a\a.py", line 15, in exec
    b = eval('B()');
  File "<string>", line 1, in <module>
NameError: name 'B' is not defined

Then the questions are:

  1. Why my class B are not available to my A.exec() if in the file that I created the class I had imported both classes A and B?

  2. How to fix it? What means make any class in my system available to be created by any class even inside a separate module (like 'eval' module in this example)?

  3. There are a best way to create these class dynamically than use eval function?

Thanks for your support.

1

There are 1 best solutions below

1
Fernando Lima On

I just found a manner to get all modules loaded.

This is a possible solution:

import sys

def get_user_modules() -> list:
    ret = []
    sm = sys.modules
    for k in sm.keys():
        if (k[0] != '_'):
            if (not k.startswith('encodings.')):
                if (not (k in ['sys', 'builtins', 'nt', 'marshal', 'winreg', 'time', 'zipimport', 'codecs', 
                            'encodings', 'abc', 'io', 'stat', 'genericpath', 'ntpath', 'os', 'site', 'os.path'])):
                    ret.append(k)
    return ret

This code will return:

['eval.a.a', 'eval.a', 'eval.b.b', 'eval.b', 'eval']

For the system structure used in the example.

I think that the final solution should be based on this list of modules loaded.

Thanks to everyone who tried to help.

Updated:

Another possibility is to go through the file system from the starting point to the last folder, as shown here:

def get_all_modules() -> list:
    r = os.path.dirname(sys.argv[0])
    t = len(r)
    l = []
    for root, dirs, files in os.walk(r):
        s = root[t:].replace('\\', '.')
        for f in files:
            if (f.endswith('.py') and (f[0:2] != '__')):
                fn = f[:-3]
                if (s != ''):
                    fn = s[1:] + '.' + fn
                l.append(fn)
    return l

And then, I can just use:

def import_all_modules() -> str:
    um = get_all_modules()
    s  = ''
    for k in um:
        if (k != __name__):
            s = s + f'from {k} import *\n'
    return s

To get all that I need to import and execute this doing:

exec(import_all_modules())

So, I will have dynamically imported all the classes defined within the application.