Search code examples
pythonimportpackagerelative-import

Python - Referenced packages to be attributes of other packages


How do I make one package reference another package, but make it look like the referenced package belongs to the initial package?

package_1
|
|____ __init__.py

package_2
|
|____ __init__.py
|____ module_1.py

Inside package_1.__init__.py is

# __init__.py
import package_2

Now the following calls works fine:

>>> import package_1
>>> print package_1
<module <module 'package_1' from  '...\package_1\__init__.pyc'>

>>> from package_1 import package_2
>>> print package_2
<module 'package_2' from '...\package_2\__init__.pyc'>

>>> print package_2.module_1
<module 'package_2.module_1' from '...\package_2\module_1.pyc'>

>>> from package_2 import module_1
>>> print module_1
<module 'package_2.module_1' from '...\package_2\module_1.pyc'>

But I want to be able to do this:

>>> from package_1.package_2 import module_1

But I get:

Traceback (most recent call last):
    File "testfile.py", line 15, in <module>
        from package_1.package_2 import module_1
ImportError: No module named package_2

The reason I want to do this is because package_2 used to be a subpackage of package_1. Now that package_2 is it's own package, I want to be able to reference it from package_1 to keep the behaviour as it was before.


Solution

  • You can add a package_2.py module under package_1 that simply contains:

    from package_2 import *
    

    If you are using python2 you should add:

    from __future__ import absolute_import
    

    At the top of the file in order to have python3's behaviour.

    In this way package_1.package_2 will be different from package_2, i.e.:

    import package_1.package_2
    import package_2
    print(package_2 is package_1.package2)   #False
    

    However all the objects inside package_1.package_2 are the same of the external module.

    This has the limitations that if you want to import a submodule, and not just something defined in package_2/__init__.py's, you get an ImportError. If you want to avoid this you can:

    • Use specific imports to import all the stuff you need:

      from package_2 import *
      from package_2 import submodule1, submodule2, subpackage3
      
    • Add an _all.py module under package_2 that imports all the stuff you want exposed and import from that

    Surely *-imports are often avoided, but in this case, it seems like the only solution to your problem that doesn't required hacking the importing mechanism.

    If you really hate this the other way I can think of is to write a custom importer using importing hooks, but this would require quite a bit of work to get it right.

    Also, since package_2 is now its own package you should probably consider deprecating the old way of importing in order to be able to remove this "hack" in some future release. Probably it's not worth it to spend too much time writing an import hook if you are going to get rid of it in the future anyway.


    Note that the from import:

    from package_1.package_2 import module1
    

    Requires you to actually provide a package_1.package_2 module or subpackage.

    The imports of the form:

    from X.Y.Z import something
    

    First import X, then the importer will search for a file called Y.py under the X directory. Then it will search a Z.py file under X/Y or the directory X/Y/Z finally, if something is found in the imported Z module it is simply placed in the scope, otherwise something is a module that is imported.

    However this last step, checking whether something already exists in Z, is only performed when all of X, Y and Z were completely imported. In your use case:

    from package_1.package_2 import module1
    

    You simply cannot have package_2 simply defined inside package_1.