14

Is there any alternative to prevent typing repeated long paths while importing modules in python.

Current code:

from a.b.c.d.e.f.g import g1
from a.b.c.d.e.f.g import g2
from a.b.c.d.e.f.g.h import h1
from a.b.c.d.e.f.g.i import i1

I tried following code:

ref_path = a.b.c.d.e.f.g
from ref_path import g1
from ref_path import g2
from ref_path.h import h1
from ref_path.i import i1

But unfortunately it did not work. I cannot do from a.b.c.d.e.f.g import * since there might be too many modules within the ref_path.

If I am able to do this, I could easily maintain different common ref_paths used in different modules from one common location with minimal efforts.

T-Bag
  • 10,916
  • 3
  • 54
  • 118
Ulysses
  • 5,616
  • 7
  • 48
  • 84

2 Answers2

8

There is no miracle cure for this problem.

There are, however, a few strategies you can use to make your imports a little bit nicer:

  • Import all names from a module at the same time

    Instead of

    from a.b.c.d.e.f.g import g1
    from a.b.c.d.e.f.g import g2
    

    use

    from a.b.c.d.e.f.g import g1, g2
    
  • Use relative imports (if you're importing within your own package)

    Assuming that this import is happening in a.b.c.d.e, you can replace

    from a.b.c.d.e.f.g.h import h1
    

    with

    from .f.g.h import h1
    

    This also works if you're in a sibling (sub-)module. For example, if this import is taking place in a.b.c.d.e.x.y, you can use

    from ...f.g.h import h1
    

    For more details about relative imports, see

  • Refactor your package (if it's your own code)

    If your package has more than 4 levels of submodules (like a.b.c.d.e), there's a pretty high chance that you should rethink your package structure. At that point you're really just shooting yourself in the foot. Perhaps c could be a standalone package, outside of a. Or perhaps e doesn't really need to be inside d and can be moved up a level or two. To quote the Zen of Python: Flat is better than nested.

Critique of other suggestions

  • importlib.import_module

    Someone suggested this:

    import importlib
    ref_path = 'a.b.c.d.e.f.g'
    
    g1 = importlib.import_module(ref_path).g1
    h1 = importlib.import_module(ref_path).h.h1
    

    This serves absolutely no purpose. Now you have to write importlib.import_module(ref_path) instead of from a.b.c.d.e.f import. It's not shorter. It's not more readable. It's nothing but a more verbose version of the next suggestion. (Read on...)

  • Assigning a.b.c.d.e.f to a variable

    The next suggestion was this:

    import a.b.c.d.e.f.g as ref
    
    g1 = ref.g1
    h1 = ref.h.h1
    

    This looks nice, but it doesn't always work. If the g module doesn't automatically import the h submodule, this code will throw an AttributeError. To demonstrate:

    >>> import urllib as ref
    >>> urlopen = ref.request.urlopen
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: module 'urllib' has no attribute 'request'
    >>>
    >>> from urllib.request import urlopen  # this works, though
    >>>
    

    If this is inside your own package, there's an additional disadvantage to this solution: Your IDE most likely won't understand your imports, and won't automatically update them for you if you ever refactor your package structure. In the long run, old-fashioned import statements are more maintainable.

Aran-Fey
  • 39,665
  • 11
  • 104
  • 149
7

You can try to use importlib as follows:

import importlib
ref_path = 'a.b.c.d.e.f.g'

g1 = importlib.import_module(ref_path).g1
h1 = importlib.import_module(ref_path).h.h1

Edit: Another way of doing it could be to import the ref path as a package and then assigning the sub-modules to local variables as follows:

import a.b.c.d.e.f as ref

g1 = ref.g1
h1 = ref.h.h1
b-fg
  • 3,959
  • 2
  • 28
  • 44
  • Also, `importlib.import_module(ref_path).h.h1` will crash if `h` hasn't been imported yet. – Aran-Fey Mar 25 '19 at 08:35
  • From a readability point of view it is an improvement. OP surely knows how to import multiple submodules from a parent module by separating them with commas. The main point was to avoid writing the full path every time, not to do it in less lines... – b-fg Mar 25 '19 at 08:50
  • Will it still be more readable after you fix that bug that throws an AttributeError in `importlib.import_module(ref_path).h`, though? – Aran-Fey Mar 25 '19 at 08:51
  • I can't see that bug in my system... MCVE: `import importlib`; `ref_path = 'numpy'`; `rand = importlib.import_module(ref_path).random.rand`; `rand()`. Works just fine. – b-fg Mar 25 '19 at 08:53
  • 2
    That's because numpy imports the `random` submodule automatically. Try `importlib.import_module('urllib').request`. – Aran-Fey Mar 25 '19 at 08:55
  • 1
    I have tried this in my PyCharm Python 3.6 interactive shell and works fine too. Outside the IDE I do get the Attribute Error. – b-fg Mar 25 '19 at 08:56