Python relative import to invoke script from random directory - python

I have the following directory structure:
test1/
test1/a.py
test1/test2/b.py
b.py needs to import a class in a.py. So I can add the following line to b.py before importing a.
sys.path.append(os.path.dirname(sys.argv[0]) + "/..")
This works and I can invoke b.py from any directory and it is able to import a.
But this fails when I write a script in another directory to invoke this file using execfile().
I tried relative imports but I get a "Attempted Relative Import in Non-Package error"
from ..a import someclass as cls
I have __init__.py in both test1, test2
Does someone have an idea how to make it work?
Is PYTHONPATH the way to go?

The problem is that execfile will evaluate the file you are calling as pure python code. Every relative import statement inside of b.py (and any package module imported by it) will have to remain true to your calling script.
One solution is to not use any relative import paths within the package. Make sure test1 package is on your PYTHONPATH as well.
b.py
from test1 import a
With test1 in your PYTHONPATH, the import of a should success in your execfile
>>> import sys
>>> sys.path.append('/path/to/parent/of_test1')
>>> execfile('/path/to/parent/of_test1/test1/test2/b.py')

Related

Handle import of modules inside of a project

I have the following project architecture (simplified):
root/
main.py
mypackage/
__init__.py
module1.py
module2.py
ci_scripts/
script1.py
script2.py
doc/
doc
where the root is nothing more than a folder.
How do I import module1 and module2 in script1.py for example?
I tried to:
Add the relative path inside script1.py: from ..mypackage import module1
The absolute path
Use of sys package to append the path of mypackage
To give more context, I was expecting that the following code inside of script1.py would work:
from ..mypackage import module1
module1.func1()
but I get:
ImportError: attempted relative import with no known parent package
And when I try to use the absolute path:
import mypackage.module1
module1.func1()
I get the following error:
ModuleNotFoundError: No module named 'mypackage'
Your absolute import probably does not work because your root folder is not set to be mypackage. You can see here on how to do that: python: Change the scripts working directory to the script's own directory
Alternatively, you can use relative imports. You are correctly importing with from ..mypackage import module1 - however, you cannot execute a script directly with a relative import. You need to import the script into some other module which you are then executing. See explanation here: Relative imports in Python 3
I've had similar problems in the past and thus I've created a new import library: ultraimport
It gives you more control over your imports and lets you do file system based relative and absolute imports that do always work.
In your script1.py you could then write:
import ultraimport
module1 = ultraimport("__dir__/../mypackage/module1.py")
This will always work, no matter how you run your program and independent of all external factors.

Importing a module from subdirectory causes it to not find other modules in its directory

I have the following structure
> external.py
- folder1
- folder2
> a.py
> b.py
File a.py:
from b import functionB
File external.py:
from folder1.folder2.a import functionA
If I now call python a.py everything works. But calling python external.py results in:
ModuleNotFoundError: No module named 'b'
If I change the import in file a.py to:
from .b import functionB
then calling python external.py works, but python a.py results in:
ImportError: attempted relative import with no known parent package
Why is module b suddenly not visible anymore when I import module a from outside the folder structure?
I tried adding __init__.py files at several places without much success.
Add a try except statement in a.py and that should work fine, this happens as working directory changes when you import the a.py from external.py
try:
from b import functionB
except ImportError:
from folder1.folder2.b import functionb

Why importing in the imported modules works differently?

If I have
a.py
b.py
In b.py I can import a
But if I have
c.py
m
a.py
b.py
and in c.py do import m.b, suddenly in b.py I get ModuleNotFoundError: No module named 'a'
What's the problem? I don't see how the second case is any different from the first
So... the modules are searched in the directory of the module that was started initially. I just don't understand the reasoning.
I'm not asking how to fix the problem. But rather asking why there's a problem in the first place...
(tested on Python 3.8.8)
It's based what file you run python.
If you run with python c.py
file c.py use:
from m import a, b
file b.py also use:
from m import a
If you go python b.py you just need import a to use in b.py
so depend what file you run it's will be the parent for relative import.
Let's start with why your first one works.
Assuming you have a file structure such that:
c.py
m - |
|
__init__.py
a.py
b.py
Let's say your current directory is within the m folder and you run the command.
python b.py
If within your b.py you place something like:
import a
import sys
print(sys.path)
it will work just fine. But let's take a look at the output from print(sys.path).
On windows, this is will look something like ['C:\\path\\to\\myprogram\\m', '<path to python installation>', etc.]
The important thing is that the m folder is on your sys.path and that is how import a gets resolved.
However if we go up one level and run:
python c.py
you should immediately notice that m is no longer on your sys.path and instead is replaced by 'C:\\path\\to\\myprogram'.
That is why it fails. Python automatically includes your current working directory in sys.path and changing out of it means it no longer knows where to look for the import.
This is an example of an absolute import. You can manipulate where python looks for these imports by modifying sys.path to include the path of the file you want to import.
sys.path.append('C:\\path\\to\\myprogram\\m')
But there's a better way to do it. If m is a package or sub-package (includes an __init__.py) you can use a relative import.
from . import a
However, there is a small caveat to this.
You can only use relative imports when the file using them is being run as a module or package.
So running them directly as a top level script
python b.py
will produce
ImportError: attempted relative import with no known parent package
Python luckily has the ability to run a script as a module built in.
if you cd one level up so as to encapsulate your m package, you can run
python -m m.b
and have it run just fine.
Additionally, since b.py is being treated as a module in c.py because of the import, the relative import will work in this case as well.
python c.py

Why do I have to use a relative import in a package's __init__.py?

Setup
test/
main.py
pkg/
a.py
__init__.py
main.py contains:
import pkg
pkg.a
__init__.py contains:
from . import a
main.py can be run without errors.
Question
Changing the content of __init__.py to
import a
gives the following error when running main.py:
Traceback (most recent call last):
File "C:/Users/me/PycharmProjects/test/main.py", line 1, in <module>
import pkg
File "C:\Users\me\PycharmProjects\test\pkg\__init__.py", line 1, in <module>
import a
ModuleNotFoundError: No module named 'a'
Interestingly, __init__.py can be executed directly with python __init__.py without errors.
What's going on?
When you run a python script, it's parent folder is added to sys.path
run main.py: sys.path[0] = '../test'
run init.py: sys.path[0] = '../test/pkg'
Your case: You try to "absolute-like" import a in __init__.py but the parent folder of a.py - which is '../test/pkg' - is not in the sys.path when you run main.py. This is why you get an error. However, your absolute import is incomplete as it should always start at the top level folder, e.g.
from test.pkg import a
Final answer to your question: You don't have to use relative imports!
See: PEP-8: Absolute imports are recommended, as they are usually more readable and tend to be better behaved (or at least give better error messages) if the import system is incorrectly configured (such as when a directory inside a package ends up on sys.path).
And keep in mind that relative imports don't work in a top-level-script when __name__ = "__main__", but from imported modules only.
You can learn more about absolute and relative imports here:
Absolute vs. explicit relative import of Python module
https://realpython.com/absolute-vs-relative-python-imports/
I suppose you are using Pycharm? Then that's one of the confusion cause.
For example, let's say your directory looks like this
project1
p1.py
test/
__init__.py
main.py
pkg/
a.py
__init__.py
If you run (F10) the main.py your default working directory will be project1/test, which does not contain the a.py so import a will not find anything.
But if you run (F10) the pkg/__init__.py your working directory will be project1/test/pkg which has the a.py, and it works like what you tested.
So in these situation, if you use from . import a it will look for the directory that file is, project1/test/pkg in this case, which will always work regardless your working directory.

Python: Importing a file from a parent folder

...Now I know this question has been asked many times & I have looked at these other threads. Nothing so far has worked, from using sys.path.append('.') to just import foo.
I have a python file that wishes to import a file (that is in its parent directory). Can you help me figure out how my child file can successfully import its a file in its parent directory. I am using python 2.7.
The structure is like so (each directory also has the __init__.py file in it):
StockTracker/
└─ Comp/
├─ a.py
└─ SubComp/
└─ b.py
Inside b.py, I would like to import a.py: So I have tried each of the following but I still get an error inside b.py saying "There is no such module a".
import a
import .a
import Comp.a
import StockTracker.Comp.a
import os
import sys
sys.path.append('.')
import a
sys.path.remove('.')
from .. import a
Should do it. This will only work on recent versions of Python--from 2.6, I believe [Edit: since 2.5].
Each level (Comp and Subcomp) must also be have an __init__.py file for this to work. You've said that they do.
When packages are structured into
subpackages (as with the sound package
in the example), you can use absolute
imports to refer to submodules of
siblings packages. For example, if the
module sound.filters.vocoder needs to
use the echo module in the
sound.effects package, it can use from
sound.effects import echo.
Starting with Python 2.5, in addition
to the implicit relative imports
described above, you can write
explicit relative imports with the
from module import name form of import
statement. These explicit relative
imports use leading dots to indicate
the current and parent packages
involved in the relative import. From
the surround module for example, you
might use:
from . import echo
from .. import formats
from ..filters import equalizer
Quote from here http://docs.python.org/tutorial/modules.html#intra-package-references
If the Comp directory is in your PYTHONPATH environment variable, plain old
import a
will work.
If you're using Linux or OS X, and launching your program from the bash shell, you can accomplish that by
export PYTHONPATH=$PYTHONPATH:/path/to/Comp
For Windows, take a look at these links:
http://docs.python.org/using/windows.html
http://www.itechtalk.com/thread3595.html
EDIT:
To modify the path programmatically, you were on the right track in your original question. You just need to add the parent directory instead of the current directory.
sys.path.append("..")
import a

Categories

Resources