1

if I have a directory like this

dir_one/
    main.py
    __init__.py
    dir_two/
        sub.py
        __init__.py

both my init.py files are currently empty. In my sub.py file, I attempt to import a class from main.py: e.g.

#File:sub.py
from main import items

Which didn't work. I also tried

#File:sub.py
from dir_one.main import items

which also didn't work.

Is there a way to import the main.py file from sub.py? Do I need to edit the __init__.py given that it is empty?

martineau
  • 119,623
  • 25
  • 170
  • 301
kwotsin
  • 2,882
  • 9
  • 35
  • 62
  • What script are you invoking? Are you trying to run `sub.py` directly (a.la `python dir_one/dir_two/sub.py`)? Or are you importing `sub.py` from elsewhere and it is failing at that point? – mgilson Aug 18 '16 at 14:54
  • i'm trying to run sub.py directly – kwotsin Aug 18 '16 at 15:02
  • Ahh -- That's the problem. Generally modules in packages aren't meant to be run directly. Usually the way these things are meant to be structured is that you should have a separate directory (e.g. `scripts` that imports the package (and utilities therein) which provides the entry point into the program. – mgilson Aug 18 '16 at 15:22
  • Meaning to say generally the modules don't import each other, but instead another non-module script import the modules? – kwotsin Aug 19 '16 at 13:45
  • Modules _can_ import each other (often using ["package-relative imports"](http://stackoverflow.com/questions/72852/how-to-do-relative-imports-in-python)), but only if the package has been imported first or if you have a `__package__` variable. This means that your `__main__` module can't be in a package unless it has a `__package__` variable.. – mgilson Aug 19 '16 at 14:39

2 Answers2

4

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)

Community
  • 1
  • 1
mgilson
  • 300,191
  • 65
  • 633
  • 696
2

In your file sub.py you could do something like

import os

wd = os.getcwd()    # save current working directory
os.chdir('path/to/dir_one')    # change to directory containing main.py  
from main import items    # import your stuff
os.chdir(wd)    # change back to directory containing sub.py

before using your items form main.

Edit: In your specific case 'path/to/dir_one' would simply be one directory upwards, i.e. '..'

obachtos
  • 977
  • 1
  • 12
  • 30