1

I am setting up a sample python package using the "Package Relative Imports" syntax, referring to this document. And it is not working, the Relative Imports in b.py ran into problems. Here are my file structure (all __init.py__ are empty)

lib/
    dir1/
        __init.py__
        a.py
    dir2/
        __init.py__
        b.py
    __init.py__
    c.py

File a.py

def a_foo(a, b):
    return a + b

File b.py

from ..dir1.a import a_foo
def b_bar():
    return a_foo(1,2)

File c.py

from dir2.b import b_bar
print(b_bar())

I ran c.py and got the following error

PS D:\tmp\py> python c.py  
Traceback (most recent call last):
  File "D:\tmp\py\c.py", line 1, in <module>
    from dir2.b import b_bar
  File "D:\tmp\py\dir2\b.py", line 1, in <module>
    from ..dir1.a import a_foo
ImportError: attempted relative import beyond top-level package

I think I structured everything according to the document. Not sure why the relative import is not working. I have a Python 3.9.7 running in Windows 10.

wovano
  • 4,543
  • 5
  • 22
  • 49
Qiao Li
  • 189
  • 1
  • 1
  • 10
  • Please do not change the question with follow-up question once you have an answer. If you have another question, follow the procedure for new questions: first search (your question about using `__main__` has been asked and answered many times before), and if you couldn't find the answer, ask a new question here. Hint: see [this question](https://stackoverflow.com/questions/419163/what-does-if-name-main-do) – wovano Jun 15 '22 at 15:01

2 Answers2

0

I think your from dir2.b is being interpreted as an absolute import, not relative. The docs you refer to say:

Try:

from .dir2.b import b_bar

Note the preceding period. It means look in the current directory for "dir2"

Then call it using

python -c "import lib.c"
Qiao Li
  • 189
  • 1
  • 1
  • 10
astrochun
  • 1,642
  • 2
  • 7
  • 18
  • 1
    Yes, I was just about to write the same. And calling `python c.py` is wrong as well, but `python -c "import lib.c"` from the parent directory will give the expected result after your suggested modification. – wovano Jun 15 '22 at 13:03
  • Personally I've just gone all the way and configure with a pyproject.toml/setup.cfg and setup.py. All scripts calling the library have absolute imports while anything within in library is relative. This has never failed me and ensures portability. – astrochun Jun 15 '22 at 13:39
  • Thanks, guys. I am new to Python. I think I need to more study how the file structure works when building a project with many folders and files. I made my code work for now using your comments. But do not quite understand how it all works. – Qiao Li Jun 15 '22 at 14:04
  • @QiaoLi, I have quite some experience with Python, but I think the importing system is one of the most complex things in Python and it has often confused me as well. It's not the easiest thing to start with. But I'm glad you got it working :-) – wovano Jun 15 '22 at 15:00
-1

To get a simple idea Lets look at the directory tree again

lib
├── c.py
├── dir1
│   ├── a.py
│   └── __init__.py
├── dir2
│   ├── b.py
│   └── __init__.py
└── __init__.py

Important things

  1. In this tree, the top most __init__.py file is in the root directory.
  2. All other subfolders with python scripts are included __init__.py

OK, Here comes the point. When you are trying to do relative import with .. it tries to import from lib.dir1.a, But there is no __init__.py at the same level with lib folder. That's why you get ImportError: attempted relative import beyond top-level package error. Because of python package importer doesn't identify lib folder as the top level package.

When you are trying to relative import with . , you checks dir2.dir1.a , which doesn't exit. Sow it will give you ModuleNotFoundError: No module named 'dir2.dir1' error

OK lets check whether it's true or not. lets add new folder dir3 within dir1 and d.py,e.py and __init__.py inside it

Here is the NEW directory tree

lib
├── c.py
├── dir1
│   ├── a.py
│   ├── dir3
│   │   ├── d.py
│   │   ├── e.py
│   │   └── __init__.py
│   └── __init__.py
├── dir2
│   ├── b.py
│   └── __init__.py
└── __init__.py

And here are the NEW file contents

a.py

def a_foo(a, b):
    return a + b

b.py

from dir1.a import a_foo
def b_bar():
    return a_foo(1,2)

e.py

def e_foo(a, b):
    return a + b

d.py

from ..a import a_foo
from .e import e_foo
def d_bar():
    return a_foo(1,2) 

def d_foo():
    return e_foo(1,2) 

.. , Which tries to import from dir1.a, which exists and also which is accessible.

. , Which tries to import from dir3.e, which exists and also which is accessible.

c.py

from dir2.b import b_bar
from dir1.dir3.d import d_bar,d_foo
print("b_bar:", b_bar())
print("d_bar:", d_bar())
print("d_foo:", d_foo())

Now lets run c.py. The result is

b_bar: 3
d_bar: 3
d_foo: 3

Which indicates our relative import has been successful inside d.py.

OK then. I think this will solve your problem. We can use relative import in files which are in depth from the root-package. But when they are too close (within one or two levels), it's impossible.

Actually we can use . to import from python scripts in same level but not when the script is in the top most directory.

Kavindu Ravishka
  • 711
  • 4
  • 11