2

Given the following tree:

├── main.py
└── my_module
    ├── a.py
    ├── b.py
    └── __init__.py

a.py:

def f():
    print('Hello World.')

b.py:

from a import f

def f2():
    f()

if __name__ == '__main__':
    f2()

main.py:

from my_module.b import f2

if __name__ == '__main__':
    f2()

When I run b.py, "Hello World." is printed successfully. However, when I run main.py, I get the following error:

Traceback (most recent call last):
  File "/home/user/main.py", line 1, in <module>
    from my_module.b import f2
  File "/home/user/my_module/b.py", line 1, in <module>
    from a import f
ModuleNotFoundError: No module named 'a'

I would expect the same output executing main.py and b.py

Keredu
  • 378
  • 1
  • 14
  • Does this answer your question? [How can I do relative imports in Python?](https://stackoverflow.com/questions/72852/how-can-i-do-relative-imports-in-python) – Tzane Aug 31 '23 at 13:22
  • Not exactly because I have a.py and b.py under the same directory. In the link you provided, it looks like a.py would be under one directory and b.py under another one. – Keredu Aug 31 '23 at 13:25
  • I found this https://stackoverflow.com/questions/66445974/how-to-import-a-module-from-outside-the-directory but I think it's the opposite case, since it would be like importing in a.py from main.py – Keredu Aug 31 '23 at 13:27
  • Using relative import and running `a.py` with `python -m my_module.a` still works and you don't have to do any conditional imports. – Tzane Sep 01 '23 at 06:19
  • @Keredu I have tested the code you have accepted and it works also without the `__init__.py` file. I write this only for completeness. For me too this topic is very interesting. – frankfalse Sep 01 '23 at 07:03

2 Answers2

3

Because you have an __init__.py file in the my_module directory, Python sees the entirety of my_module as a package. That also means that all imports within my_module must be relative imports.

This is easily fixed by changing your import in b.py to be a relative import.

b.py

from .a import f

def f2():
    f()

if __name__ == '__main__':
    f2()

If you want to be able to run b.py as a separate script in addition to importing it as part of the the my_module package. You can make the imports conditionally relative or absolute based on whether __name__ == "__main__"

if __name__ != '__main__':
    from .a import f

def f2():
    f()

if __name__ == '__main__':
    from a import f
    f2()

See also the definitive explanation of relative imports on SO

nigh_anxiety
  • 1,428
  • 2
  • 4
  • 12
  • This is exactly the problem I had since I tried to run b.py and main.py. I have never seen the differentiation of imports destinguishing with != and == __main__. How is it done in big projects? Or what I'm doing is a bad practice? (i.e., both using b.py to run things and be used to import functions in main.py) – Keredu Aug 31 '23 at 13:55
  • Someone with more experience on big Python projects may be able to give a better answer, but my understanding is that you would typically keep your modules separate from what you intend to run as a top level script. If you want a separate primary script within `my_module`, then make an additional script within the package, such as `app.py`, or `main.py`, and that script can use absolute imports within the package. – nigh_anxiety Aug 31 '23 at 15:12
  • 1
    Each individual module could have some code within the `if __name__ == '__main__'` block to run some basic tests or print some info or something, but the module files should generally be treated like libraries, not intended to be run directly as a top level script. You can also utilize __init__.py to determine [which symbols from the module should and should not be imported when using `from my_module import *`](https://stackoverflow.com/questions/44834/what-does-all-mean-in-python) or do any other setup needed when importing the module. – nigh_anxiety Aug 31 '23 at 15:12
0

You can modify main.py as following:

if __name__ == '__main__':
    import sys
    sys.path.insert(1, './my_module')
    from my_module.b import f2

    f2()

In this way you add my_module in the path where to search your module.

frankfalse
  • 1,553
  • 1
  • 4
  • 17
  • In that case, it looks like __init__.py it's not necessary. When importing my_module.b, isn't it supposed to be required the __init__.py to treat my_module as a python module? – Keredu Aug 31 '23 at 13:39
  • Yes with my solution the `__init__.py` file is not necessary. – frankfalse Aug 31 '23 at 13:41
  • 2
    Hacking `sys.path` should really be avoided when there is a much better solution of simply using imports correctly. – nigh_anxiety Aug 31 '23 at 13:41