You have a few options here.
Your best option is to keep packages separate from scripts that use the package. Have a look at entry_points
for setuptools. You point setuptools to a function in your package and it creates a script to call it. Nifty...
In playing around with this, I've set up the following package structure:
test_dir
+ __init__.py
+ main.py
+ sub1
--+ __init__.py
--+ script.py
+ sub2
--+ __init__.py
--+ module.py
and I've made sure that test_dir
is accessible via PYTHONPATH
.
The scripts are all super simple (just printing some stuff):
# main.py
def func_in_main():
print("Hello from main.py!")
# module.py
def run_func():
print("Hello from script in sub2.py!")
# script.py
from ..sub2 import module
from .. import main
def entry_point():
module.run_func()
main.func_in_main()
if __name__ == '__main__':
entry_point()
Now, what happens if I try to run this directly?
$ python test_package/test_dir/sub1/script.py
Traceback (most recent call last):
File "test_package/test_dir/sub1/script.py", line 2, in <module>
from ..sub2 import module
ValueError: Attempted relative import in non-package
Hmm ... Bummer (this is the case that I was trying to describe in the comments on your original question). This happens regardless of my current working directory by the way ... However, from anywhere on my filesystem, I can run this using the -m
flag1:
$ python -m test_dir.sub1.script
Hello from script in sub2.py!
Hello from main.py!
Horray! We only need to specify the module path and then we're golden (remember, test_dir
MUST be reachable via your PYTHONPATH for this to work). Ok, but what if we really want to call the script instead of using the module path? If this is the case, we can modify the __package__
variable before we do any importing:
# script.py (updated)
if __name__ == '__main__' and __package__ is None:
__package__ = 'test_dir.sub1'
import test_dir # needed to suppress SystemError -- I'm not sure why...
from .. import main
from ..sub2 import module
def entry_point():
module.run_func()
main.func_in_main()
if __name__ == '__main__':
entry_point()
Now lets try to run this thing directly again:
$ python test_package/test_dir/sub1/script.py
test_package/test_dir/sub1/script.py:4: RuntimeWarning: Parent module 'test_dir.sub1' not found while handling absolute import
import test_dir
Hello from script in sub2.py!
Hello from main.py!
We still get a RuntimeWarning
, but it runs Ok. For more details, have a look at PEP-0366.
1In general, I've run most of these from outside the package (one level above test_dir
), but the examples work if I run it from inside the package as well. with -m
you always specify the full path to the module (test_dir.sub1.script
), without it, you just specify the relative or absolute path to the file)