0

I have the following structure of python code:

.
├── my_main.py
└── my_pkg
    ├── my_dep.py
    ├── my_script.py

Both my_main.py and my_script.py should be callable (have a if __name__ == '__main__') section:

my_main.py:

import my_pkg.my_script 
if __name__ == '__main__':
    print(my_pkg.my_script.bar())

and my_script.py:

import my_dep
def bar():
    return my_dep.foo() + 1
if __name__ == '__main__':
    print(bar())

this imports ... my_dep.py:, which looks like:

def foo():
    return 1

If you want to look at it all together, look here: https://github.com/ct2034/python_import_trouble

Problem:

  • If I run my_script.py, all works well.

    But if I run my_main.py, I get:

    ModuleNotFoundError: No module named 'my_dep'

  • If I change the import in my_script.py to from . import my_dep, my_main.py works.

    But when I run my_script.py, I get:

    ImportError: attempted relative import with no known parent package

How can I make both of them work?

Note: This is on Python 3.8

And sorry for the long-winded explanation. Was not able to make it any more concise. Hope it is understandable.

Christian
  • 1,341
  • 1
  • 16
  • 35
  • I would suggest creating a setup.py for your project and doing an editable install with `pip install -e .` (see https://stackoverflow.com/a/69228512/3216427 for example), and then `my_script.py` will be able to `import my_pkg.my_dep` successfully. – joanis Jan 05 '22 at 14:39

3 Answers3

2

Both my_main.py and my_script.py should be callable (have a if name == 'main') section)

No they should not.

What you want to do is a non Pythonic design, and even if you manage to achieve it with some work you will later be bitten. And the later is the worse because it could break more things including in production code.

I strongly urge you to stick to the common rules:

  • modules can only be found if they are in sys.path
  • relative imports only make sense in packages
  • a module inside a package should never be called as a script

It will indeed force you to adapt your design, but it will work smoothly and could easily be packaged into a distribution.

My advice if you have multiple related modules is to design that as a package (with __init__.py files). You will then get natural relative imports.

If you additionaly need to start scripts, you can either build wrapper scripts using the package modules or use a __main__.py file in your top level package folder: it will be executed if your run python -m package. But please, please do not try to run the same file sometimes as a package module and sometimes as a script. Or at least do not expect me to help you on that way...

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • Disclaimer: I now avoid this kind of design because I already tried to achieve it, spent (and lost) a lot of time on it and finaly realized that there was no *correct* way... – Serge Ballesta Jan 05 '22 at 14:42
  • Thanks. I appreciate your comment. I know that this is not pythonic and that I should be doing it differently. But honestly, at the moment, I would rather have this setup working as is and not having to rearrange all my code into the proper module-based pythonic architecture. – Christian Jan 06 '22 at 07:47
  • Like I have said in a previous comment, I have already tried and found no way... I really cannot help you that way. I understand that it is not the expected answer, but unfortunately it is the best I can post here. – Serge Ballesta Jan 06 '22 at 07:54
  • Thanks. I found a way https://stackoverflow.com/a/70604029/1493204 But I'll still upvote your answer and not mine ;-) – Christian Jan 06 '22 at 07:57
  • 1
    @Christian: I did because it can help others :-) – Serge Ballesta Jan 06 '22 at 08:05
  • 1
    Christian - making my_dep and my_script easily importable packages is easier than you maybe think. :) just add empty script /__init__.py inside my_pkg. Then you can refer to them always with ”from my_pkg import my_dep” - no more rewriting than that. I also hesitated with that in the beginning but it turned out to be very easy. :) – MattiH Jan 06 '22 at 08:10
1

I found a way to make it work. Serge is absolutely right in saying that this should be done differently altogether (https://stackoverflow.com/a/70594758/1493204). But if you still want it, this is how to do it:

my_script.py:

if __name__ != '__main__':
    from . import my_dep
def bar():
    return my_dep.foo() + 1
if __name__ == '__main__':
    import my_dep
    print(bar())

also here .. https://github.com/ct2034/python_import_trouble/tree/bad_solution

Christian
  • 1,341
  • 1
  • 16
  • 35
0

Setup your code to use absolute imports like from my_pkg import my_dep. Then you can run the scripts like that:

  • my_main.py can be executed directly
  • my_script.py can be executed with python -m mypkg.my_script

If this is meant to become an installable package, you can use the setup.py to create entry points or scripts, which will be installed into some system wide accessible bin directory anyway.

Wups
  • 2,489
  • 1
  • 6
  • 17
  • Thanks but this is not really what I want, either. it is probably not super obvious from this toy-example but what I want to call from `my_main.py` is the method defined in `my_script`, not the whole file. – Christian Jan 06 '22 at 08:01
  • 1
    @Christian I updated my answer with better instructions. – Wups Jan 06 '22 at 10:10