How to get Python relative imports right - python

I have a custom Python library ("common") that is being imported and used from several Python projects.
That central library has the following structure:
/common
/__init__.py
/wrapper.py
/util
/__init__.py
/misc.py
Our custom library resides in a central place /data/Development/Python, so in my Python projects I have an .env file in order to include our lib:
PYTHONPATH="/data/Development/Python"
That works fine, I can for example do something like:
from common.util import misc
However, now I want to make use of a class MyClass within common/wrapper.py from the code in common/util/misc.py. Thus I tried the following import in misc.py:
from ..wrapper import MyClass
But that leads to the following error:
Exception has occurred: ImportError
cannot import name 'MyClass'
Any ideas what I am doing wrong here?
PS: When I do an from .. import wrapper instead and then from code I use wrapper.MyClass, then it works fine. Does that make any sense?

It's finding wrapper, otherwise you'd get a different error:
>>> from wibble import myclass
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'wibble'
So it seems wrapper does not contain MyClass. Typo?

"However, now I want to make use of a class MyClass within common/wrapper.py from the code in common/util/misc.py. Thus I tried the following import in misc.py:"
If you do export PYTHONPATH=~/common
In order to import MyClass you will do:
from wrapper import MyClass
Because you have added common to your root folder, utils is already in your path since it is in you root folder.
Similarly, if you wanted to import from wrapper you would do from utils.misc import ThisClass since utils.misc is already in the root folder common

Related

Python: Circular Import error, eventhough no "from ... import ..." used

I am struggeling with resolving a circular import issue. My setup is as follows:
Module a.py:
import b
class A():
pass
Module b.py:
import a
class Bar(a.A):
pass
Notice the circular import in module b: is needs to access class A from module a, because B inherits from A.
Finally, the main module, main.py:
import a
# ...
When I run main.py, I get this error:
$ python3 main.py
Traceback (most recent call last):
File "/home/.../main.py", line 1, in <module>
import a
File "/home/.../a.py", line 1, in <module>
import b
File "/home/.../b.py", line 3, in <module>
class Bar(a.A):
AttributeError: partially initialized module 'a' has no attribute 'A' (most likely due to a circular import)
I thought, one way of resolving circular imports was to not use the from ... import ... statement, which I didn't do as you can see. But why am I still getting that error then?
EDIT: My current code structure requires that A and B are separate modules. Nevertheless, in this video, he is using a similar structure as mine, and he has no problems...
The ideal answer in such cases is usually to
combine the files or their contents (do you actually need more files?)
create a 3rd file and put shared contents into it (avoids breaking existing imports as you can simply import whatever you need into the existing libraries from the shared one)
create a 3rd file which imports both (cleaner with 3rd party libraries, but may break existing logic)
import a
import b
class Bar(a.A):
pass

Having trouble with imports in Python

I use Python 2.7. I'm trying to run my UI-automation script, but I got ImportError.
I have at least 30 Classes with methods. I want to have these methods in each and any class that's why I created BaseClass(MainClass) and created objects of all my classes. Please advise what should I do in this case or how I can solve this problem.
Here the example what similar to my code.
test_class/baseclass.py
from test_class.first_class import FirstClass
from test_class.second_class import SecondClass
class MainClass:
def __init__(self):
self.firstclass = FirstClass()
self.secondclass = SecondClass()
test_class/first_class.py
from test_class.baseclass import MainClass
class FirstClass(MainClass):
def __init__(self):
MainClass.__init__(self)
def add_two_number(self):
return 2 + 2
test_class/second_class.py
from test_class.baseclass import MainClass
class SecondClass(MainClass):
def __init__(self):
MainClass.__init__(self)
def minus_number(self):
return self.firstclass.add_two_number() - 10
if __name__ == '__main__':
print(SecondClass().minus_number())
When I run the last file I get this error
Traceback (most recent call last):
File "/Users/nik-edcast/git/ui-automation/test_class/second_class.py", line 1, in <module>
from test_class.baseclass import MainClass
File "/Users/nik-edcast/git/ui-automation/test_class/baseclass.py", line 1, in <module>
from test_class.first_class import FirstClass
File "/Users/nik-edcast/git/ui-automation/test_class/first_class.py", line 1, in <module>
from test_class.baseclass import MainClass
ImportError: cannot import name MainClass
check this line: from test_class.baseclass import MainClass -> it seems like all other imports had a '_' between the names like second_class. So try maybe to write base_class. who knows might work
Are you proabably running your code like python test_class/second_class.py. If you just do this then python will think the base directory to find modules is ./test_class. So when you import the test_class package python will start looking for a folder called ./test_class/test_class to find the sub-modules. This directory doesn't exist and so the import fails. There are several ways that you can tell python how to correctly find your modules.
Using PYTHONPATH
One way to get around this is to set PYTHONPATH before starting python. This is just an environment variable with which you can tell python where to look for your modules.
eg.
export PYTHONPATH=/path/to/your/root/folder
python test_class/second_class.py
Using the -m switch for python
Be default python treats the directory of the main module as the place to look for other modules. However, if you use -m python will fall back to looking in the current directory. But you also need to specify the full name of the module you want to run (rather than as a file). eg.
python -m test_class.second_class
Writing a root entry point
In this we just define your main module at the base level (the directory that contains test_class. Python will treat this folder as the place to look for user modules and will find everything appropriately. eg.
main.py (in /path/to/your/root/folder)
from test_class.second_class import SecondClass
if __name__ == '__main__':
print(SecondClass().minus_number())
at the command line
python main.py
You could try importing the full files instead of using from file import class. Then you would only need to add the file name before referencing something from another file.

Custom Python module structure

I am currently doing a personal coding project and I am trying to build a module, but I don't know why my structure doesn't work the way it's supposed to:
\mainModule
__init__.py
main.py
\subModule_1
__init__.py
someCode_1.py
someCode_2.py
\subModule_2
__init__.py
otherCode.py
I want to be able to run the following code from main.py:
>>> from subModule_1 import someCode_1
>>> someCode_1.function()
"Hey, this works!"
>>> var = someCode_2.someClass("blahblahblah")
>>> var.classMethod1()
>>> "blah blah blah"
>>> from subModule2 import otherCode
>>> otherCode("runCode","#ff281ba0")
However, when I try to import someCode_1, for example, it returns an AttributeError, and I'm not really sure why. Is it to do with the __init__.py file?
REVISIONS
Minimal, Complete and verifiable (I hope...)
\mainDir
__init__.py # blank file
main.py
\subDir
__init__.py # blank file
codeFile.py
Using this...
#main.py file
import subDir
subDir.codeFile.function()
And this...
#codeFile.py file
def function():
return "something"
...it returns the same problem mentioned above**.
** The exact error is:
Traceback (most recent call last):
File "C:\...\mainDir\main.py", line 2, in <module>
subDir.codeFile.function()
AttributeError: module 'subDir' has no attribute 'codeFile'
Credits to #jonrsharpe: Thanks for showing me how to use Stack Overflow correctly.
You have two options to make this work.
Either this:
from subdir import codeFile
codeFile.function()
Or:
import subdir.codeFile
subdir.codeFile.function()
When you import subDir, it does three things:
executes the code in mainDir/subDir/__init__.py (i.e. in this case does nothing, because this file is empty)
imports the resulting module under the name subDir locally, which will in turn make it an attribute of the mainDir module;
registers the new import globally in the sys.modules dictionary (because the import is being performed from a parent module mainDir, the name is completed to 'mainDir.subDir' for the purposes of this registration);
What it does not do, because it hasn't been told to, is import subDir.codeFile. Therefore, the code in codeFile.py has not been run and the name codeFile has not yet been imported into the namespace of mainDir.subDir. Hence the AttributeError when trying to access it. If you were to add the following line to mainDir/subDir/__init__.py then it would work:
import codeFile
Specifically, this will:
run the code in codeFile.py
add the resulting module as an attribute of the mainDir.subDir module
store a reference to it as yet another entry in sys.modules, this time under the name mainDir.subDir.codeFile.
You could also achieve the same effect from higher up the module hierarchy, by saying import subDir, subDir.codeFile instead of just import subDir in your mainDir.main source file.
NB: When you test this from the command line or IDE, make sure that your current working directory (queried by os.getcwd(), changed using os.chdir(wherever) ) is neither mainDir nor subDir. Work from somewhere else—e.g. the parent directory of mainDir. Working from inside a module will lead to unexpected results.

Get around circular import error when using Sphinx

I have a module robot.py with a similarly named class. I also have a cop.py module and a robber.py module, each with similarly named classes as well, each subclassing from Robot. I broke them up to keep the files shorter.
In creating a Robot object, the Robot has multiple Cop and Robber attributes (i.e., each Robot has some other cops and robbers that it keeps track of).
I've gotten around circular import issues with the following (simplified) code structure in Robot.py, where I put module import statements at the very bottom:
class Robot(object):
def __init__(self):
self.other_cop = cop_module.Cop()
self.other_robber = robber_module.Robber()
import cops_and_robots.robo_tools.cop as cop_module
import cops_and_robots.robo_tools.robber as robber_module
(Of course, each Cop.py and Robber.py both have from cops_and_robots.robo_tools.robot import Robot at the beginning of their files)
This works fine in practice. However, when I try to autodoc using Sphinx, I get the following issue:
/Users/nick/Dropbox/Syncs/Code/Github/cops_and_robots/src/cops_and_robots/docs/cops_and_robots.robo_tools.rst:41: WARNING: autodoc: failed to import module u'cops_and_robots.robo_tools.robber'; the following exception was raised:
Traceback (most recent call last):
File "/usr/local/lib/python2.7/site-packages/sphinx/ext/autodoc.py", line 335, in import_object
__import__(self.modname)
File "/Users/nick/Dropbox/Syncs/Code/Github/cops_and_robots/src/cops_and_robots/robo_tools/robber.py", line 22, in <module>
from cops_and_robots.robo_tools.robot import Robot
File "/Users/nick/Dropbox/Syncs/Code/Github/cops_and_robots/src/cops_and_robots/robo_tools/robot.py", line 520, in <module>
import cops_and_robots.robo_tools.robber as robber_module
AttributeError: 'module' object has no attribute 'robber'
Is there a good way for me to restructure my code, or to modify Sphinx, so that I don't fall into this circular dependency issue?
If you have some funky circular dependency in your python code (hey we've all been there) you may need to force Sphinx to import your dependencies in a particular order. I can't help you with which order you need to use, but you can add import statements into conf.py:
import os
import sys
sys.path.insert(0, os.path.abspath(YOUR_MODULE_PATH)
import YOUR_MODULE_DEPENDENCY_TO_RESOLVE_FIRST

Python doesn't find the modules in my package

I must be missing something very basic about building a package in Python. When I create a package following the guidelines of https://docs.python.org/2/tutorial/modules.html#packages and import it, Python does not find any of the modules. For example, say I create the package holygrail with the following structure:
holygrail/
__init__.py
knights.py
I leave __init__.py empty because the docs say that I can and I'm just trying to make a basic package to start. In knights.py I have:
def say():
print 'Ni!'
If I try import holygrail, Python doesn't give any errors, but holygrail.knights.say() results in Python telling me that the "'module' object [holygrail] has no attribute 'knights'." However, if I specifically import knights via from holygrail import knights, then knights.say() works. In addition, holygrail.knights.say() then also works.
I tried adding the line
__all__ = ['knights']
in the __init__.py file, but this did not change the behavior.
How do I construct a package such that import package loads in all of the modules, allowing statements like package.module.function()?
Python does not implicitly import the whole package hierarchy. You have to be explicit about what to import on what level of the package using the __init__.py files.
When you set __all__ = ['knights'] in the __init__.py it works only for the import all statements for that module, e.g.:
>>> import holygrail
>>> holygrail.knights.say()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'knights'
>>> from holygrail import *
>>> knights.say()
Ni!
It can also act as a filter on import all, importing from the module only what's specified.
To get knights automatically imported on import holygrail you have to put import knights or from . import knights (intra-package or relative import) to the __init__.py. You'll have to do the same for every module explicitly.
Add import knights into __init__.py.
The link you provided does state "In the simplest case, __init__.py can just be an empty file..." Your example is not the simplest case, that's all.

Categories