3

I have imports.py containing:

import os as exported_os

and foo.py containing:

from imports import exported_os
print(exported_os.path.devnull)    # works

from imports.exported_os.path import devnull    # doesn't

Is there a way to make the second import work? I tried adding __path__ to imports.py and fiddling with it but couldn't get anything.

Actual usecase: os is some_library_version_n and exported_os is some_library_version (I'm trying to avoid having many instances of some_library_version_n across different files).

5
  • is path its own module (i.e. there is a file called path.py), or is path an object that has the attribute devnull? your error looks like the latter might be the case. Commented Jan 15, 2021 at 17:07
  • @Arne os.path is a module Commented Jan 15, 2021 at 17:24
  • oh, I somehow didn't realize that you're talking about stdlibs os module.strange. Commented Jan 15, 2021 at 19:51
  • I'm giving up. I tried to find a fix by updating sys.modules dynamically, but os in particular is a strange module that does wild things in order to be importable. Next I would've tried to add a custom MetaPathFinder to sys.meta_path that redirects import imports.something.x.y.z to __import__("something_else.x.y.z"), but reading the docs on python-importing melts my brain, so I'll leave it at this vague comment here. Commented Jan 16, 2021 at 18:20
  • I don't even think you need separate files to observe this behavior, right? In imports.py you also cannot use from exported_os.path import devnull which I think would be equivalent behavior. Commented Jan 16, 2021 at 22:58

4 Answers 4

5
+25

One approach

Directory structure:

__init__.py
foo.py
imports/
 ├ __init__.py
 └ exported_os/
    ├ __init__.py
    └ path.py

imports/exported_os/__init__.py:

from . import path
from os import *  # not necessary for the question 
                  # but it makes `exported_os` more like `os`
                  # e.g., `exported_os.listdir` will be callable

imports/exported_os/path.py:

from os.path import *

In this way, you can use exported_os as if it is os with a submodule path. Different with import, from takes modules and classes.

Another approach

imports.py:

import os
import sys

ms = []
for m in sys.modules:
    if m.startswith('os'):
        ms.append(m)

for m in ms:
    sys.modules['imports.exported_os' + m[2:]] = sys.modules[m]

Or, by explicitly extending sys.modules you can use exported_os as if os with its submodules.

Why you cannot simply change the name of os

If you open .../lib/python3.9/os.py you can find the following line:

sys.modules['os.path'] = path

So even if you copy .../lib/python3.9/os.py to .../lib/python3.9/exported_os.py, the following does not work:

from exported_os.path import devnull

But if you change the line sys.modules['os.path'] to sys.modules['exported_os.path'] it works.

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks, I really want something that doesn't require me to specify all the submodules of os though.
@rlms I added another approach and more explanation. Please check and leave a comment if you cannot accept it.
@GyuHyeonChoi I think import * has to be really used with caution as you might get naming conflicts if not careful.
@mgross Should be careful. : ) In this case, exported_os contains only two lines of code to import. It would be fine.
2

The error you are getting would be something like:

ModuleNotFoundError: No module named 'imports.exported_os'; 'imports' is not a package

When you code from imports import exported_os, then imports can refer to a module implemented by file imports.py. But when the name imports is part of a hierarchy as in from imports.exported_os.path import devnull, then imports must be a package implemented as a directory in a directory structure such as the following:

__init__.py
imports
    __init__.py
    exported_os
        __init__.py
        path.py

where directory containing the top-most __init__.py must be in the sys.path search path.

So, unless you want to rearrange your directory structure to something like the above, the syntax (and selective importing) you want to use is really not available to you without getting into the internals of Python's module system.

Although this is not a solution to your wanting to be able to do an from ... import ... due to your unique versioning issue, let me suggest an alternate method of doing this versioning. In your situation you could do the following. Create a package, my_imports (give it any name you want):

my_imports
    __init__.py

The contents of __init__.py is:

import some_library_version_n as some_library_version

Then in foo.py and in any other file that needs this module:

from my_imports import *

This is another method of putting the versioning dependency in one file. If you had other similar dependencies, you would, of course, add them to this file and you could import from my_imports just the names you are interested. You still have the issue that you are importing the entire module some_library_version.

However, we could take this one step further. Suppose the various versions of your library had components A, B and C that you might be interested in importing individually or all together. Then you could do the following. Let's instead name the package some_library_version, since it will only be dealing with this one versioning issue:

some_library_version/init.py

from some_library_version_n import A
from some_library_version_n import B
from some_library_version_n import C

foo.py

from some_library_version import A, C

Comments

0

Most of the answers added are accured but dont add context of why works in that way, GyuHyeon explains it well but it just resumes it into import is a fancy file include system that checks into the std libraries, then the installed ones and finaly into the context provided, context is added on where is called and the from given.

This example gives the various method of importing a specific function dirname(), the lib os is just a folder, if you imagine that os is in your working folder the import path whoud be the same or './os' and beause python, everything is a class, so import will search for the .os/__init__.py so if your library dont have one importing the subdirs it will have no efect.

from os.path import dirname as my_fucntion  # (A_2)
from os import path as my_lib  # (B_2)

from os.path import dirname  # (C_1)
from os import path  # (B_1)
import os  # (A_1)

if __name__ == '__main__':
    print(os.path.dirname(__file__))  # (A_1)
    print(path.dirname(__file__))  # (B_1)
    print(dirname(__file__))  # (C_1)

    print(my_lib.dirname(__file__))  # (B_2)
    print(my_fucntion(__file__))  # (A_2)

Comments

0

You could try to go with sys.path.append(...), e.g.:

import sys
sys.path.append(<your path to devnull goes here>)

Maybe not so nice, but you could use the pathlib library and path joins to construct the path (but some assumptions on file structure unfortunately have to be made if you the files are in separate folder structures):

from pathlib import Path
from os import path
sys.path.append(path.join(str(Path(__file__).parents[<integer that tells how many folders to go up>]), <path to devnull>))

Instead of pathlib you could also use the dirname function from os.path. After appending to the system path, you could just use:

import devnull

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.