jsexauer
jsexauer

Reputation: 681

Absolute vs Relative Imports in Python 2

What I want is a way to import a class from a neighboring module in a package regardless of if I call the module directory or import the module into another module. I cannot figure out a way to do this kind of import.

Here is an example:

File Structure:

\test_package
    \sub_package_a
        __init__.py
        module_a.py
    \sub_package_b
        __init__.py
        module_b.py
    __init__.py
    main.py

main.py:

from sub_package_b.module_b import ClassInModuleB

b = ClassInModuleB()

module_a.py:

class ClassInModuleA(object):
    pass

module_b.py:

# I need a class in module a, this seems the most natural way to me
try:
    from test_package.sub_package_a.module_a import ClassInModuleA
except ImportError:
    print "Could not import using absolute path"
else:
    print "Imported using absolute path"

# This works, but only if importing moudle, not if running it as a script
try:
    from sub_package_a.module_a import ClassInModuleA
except ImportError:
    print "Could not import using relative path"
else:
    print "Imported using relative path"


class ClassInModuleB(object):
    pass

And here is what I observe that confuses me:

> python test_package\main.py
Could not import using absolute path
Imported using relative path


> python test_package\sub_package_b\module_b.py
Could not import using absolute path
Could not import using relative path

I would like a way to do the import that works for both run modes.

Upvotes: 2

Views: 3392

Answers (4)

EngineerCoder
EngineerCoder

Reputation: 1455

The http://docs.python.org/2/whatsnew/2.5.html#pep-328-absolute-and-relative-imports:

Absolute and Relative Imports explans very detailed.

The absolute_import feature is default in Python 3.x. (I use Python 2.7.x)

Use the examples:

pkg
├── __init__.py
├── main.py
└── string.py
The content of string.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

def say_hello():
    print "say hello"

The content of first version main.py:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import string

string.say_hello()
# move to the parent dir of pkg
$ python -m pkg.main
say hello

This will use the relative string.py module, not the Python's standard string module.

When use absolute_import:

From Python manual:

Once absolute imports are the default, import string will always find the standard library’s version. It’s suggested that users should begin using absolute imports as much as possible, so it’s preferable to begin writing from pkg import string in your code.

from __future__ import absolute_import

#import string   # This is error because `import string` will use the standard string module
from pkg import string
string.say_hello()

Relative imports are still possible by adding a leading period to the module name when using the from ... import form:

from __future__ import absolute_import

from . import string # This is the same as `from pkg import string`
string.say_hello()

or

from __future__ import absolute_import

    from .string import say_hello
    say_hello()

Use print(string) to see which string module to import


main.py:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import
import string
print(string)

string.say_hello()

If run code by:

cd pkg
$ python pkg/main.py
<module 'string' from '/path/to/my/pkg/string.pyc'>
say hello

It will always use local string.py, because current path is the first in sys.path

change main.py to:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from . import string
print(string)

string.say_hello()

run code:

cd pkg
$ python pkg/main.py
Traceback (most recent call last):
  File "pkg/main.py", line 3, in <module>
    from . import string
ValueError: Attempted relative import in non-package

This answer is detailed: https://stackoverflow.com/a/11537218/1276501

To elaborate on @Ignacio's answer: the python import mechanism works relative to the name of the current file. When you execute a file directly, it doesn't have it's usual name, but has "main" as its name instead. So relative imports don't work. You can, as Igancio suggested, execute it using the -m option. If you have a part of your package that is mean to be run as a script, you can also use the package attribute to tell that file what name it's supposed to have in the package hierarchy. See http://www.python.org/dev/peps/pep-0366/ for details.

Absolute/Relative import is to package.

In Python 2.x(by now is Python 2.7.x), the default import feature is implicit relative import.

As above see, it will first import the same-named module under package. Use absolute import as default, Python will only import by the sys.path sequence.

And if you want to use relative import, you must use explicit relative import

That as list in import this:

Explicit is better than implicit

References:

Upvotes: 4

crlb
crlb

Reputation: 1332

To make these different types of import you have to put both <root>/ and <root>/test_package/ into sys.path, from each python file you want to execute. So in main it would be:

import os
import sys
import inspect
# Get the current folder, which is the input folder
current_folder = os.path.realpath(
    os.path.abspath(
        os.path.split(
            inspect.getfile(
                inspect.currentframe()
            )
     )[0]
   )
)
folder_parts = current_folder.split(os.sep)
previous_folder = os.sep.join(folder_parts[0:-1])

sys.path.insert(0, current_folder)
sys.path.insert(0, previous_folder)

# Rest of main

And in module B it would be:

import os
import sys
import inspect

from pprint import pprint
# Get the current folder, which is the input folder
mod_b_folder = os.path.realpath(
    os.path.abspath(
        os.path.split(
            inspect.getfile(
                inspect.currentframe()
            )
     )[0]
   )
)
folder_parts = mod_b_folder.split(os.sep)
prev_test_pack_path = os.sep.join(folder_parts[0:-2])
test_pack_path = os.sep.join(folder_parts[0:-1])
sys.path.insert(0, test_pack_path)
sys.path.insert(0, prev_test_pack_path)

# Rest of module B

However, I would recommend to use one naming system for importing modules and insert the appropriate folder into sys.path.

Upvotes: 0

pnv
pnv

Reputation: 3135

Run it like this.

python -m test_package.sub_package_b.module_b

for more information,

How to fix "Attempted relative import in non-package" even with __init__.py

and

https://www.python.org/dev/peps/pep-0366/

To import relatively,

replace

sub_package_a.module_a import ClassInModuleA

with

from ..sub_package_a.module_a import ClassInModuleA

Upvotes: 0

John
John

Reputation: 1819

I believe this can be solved by manipulating the sys.path list of directories where python searches for modules. Adding the current directory '.' or even os.getcwd() before importing your module may work for you.

Something like this:

import sys,os
sys.path.append(os.getcwd())

from mymodule import MyClass

...

If you need more information on where the python source code is, or where the script is running from, take a look into the inpect python module.

Upvotes: 0

Related Questions