ChatGPT解决这个技术问题 Extra ChatGPT

Python: importing a sub‑package or sub‑module

Having already use flat packages, I was not expecting the issue I encountered with nested packages. Here is…

Directory layout

dir
 |
 +-- test.py
 |
 +-- package
      |
      +-- __init__.py
      |
      +-- subpackage
           |
           +-- __init__.py
           |
           +-- module.py

Content of init.py

Both package/__init__.py and package/subpackage/__init__.py are empty.

Content of module.py

# file `package/subpackage/module.py`
attribute1 = "value 1"
attribute2 = "value 2"
attribute3 = "value 3"
# and as many more as you want...

Content of test.py (3 versions)

Version 1

# file test.py
from package.subpackage.module import *
print attribute1 # OK

That's the bad and unsafe way of importing things (import all in a bulk), but it works.

Version 2

# file test.py
import package.subpackage.module
from package.subpackage import module # Alternative
from module import attribute1

A safer way to import, item by item, but it fails, Python don't want this: fails with the message: "No module named module". However …

# file test.py
import package.subpackage.module
from package.subpackage import module # Alternative
print module # Surprise here

… says <module 'package.subpackage.module' from '...'>. So that's a module, but that's not a module /-P 8-O ... uh

Version 3

# file test.py v3
from package.subpackage.module import attribute1
print attribute1 # OK

This one works. So you are either forced to use the overkill prefix all the time or use the unsafe way as in version #1 and disallowed by Python to use the safe handy way? The better way, which is safe and avoid unecessary long prefix is the only one which Python reject? Is this because it loves import * or because it loves overlong prefixes (which does not help to enforce this practice)?.

Sorry for the hard words, but that's two days I trying to work around this stupid‑like behavior. Unless I was totally wrong somewhere, this will leave me with a feeling something is really broken in Python's model of package and sub‑packages.

Notes

I don't want to rely on sys.path, to avoid global side effects, nor on *.pth files, which are just another way to play with sys.path with the same global effets. For the solution to be clean, it has to be local only. Either Python is able to handle subpackage, either it's not, but it should not require to play with global configuration to be able to handle local stuff.

I also tried use imports in package/subpackage/__init__.py, but it solved nothing, it do the same, and complains subpackage is not a known module, while print subpackage says it's a module (weird behavior, again).

May be I'm entirely wrong tough (the option I would prefer), but this make me feel a lot disappointed about Python.

Any other known way beside of the three I tried? Something I don't know about?

(sigh)

----- %< ----- edit ----- >% -----

Conclusion so far (after people's comments)

There is nothing like real sub‑package in Python, as all package references goes to a global dictionnary, only, which means there's no local dictionary, which implies there's is no way to manage local package reference.

You have to either use full prefix or short prefix or alias. As in:

Full prefix version

from package.subpackage.module import attribute1
# An repeat it again an again
# But after that, you can simply:
use_of (attribute1)

Short prefix version (but repeated prefix)

from package.subpackage import module
# Short but then you have to do:
use_of (module.attribute1)
# and repeat the prefix at every use place

Or else, a variation of the above.

from package.subpackage import module as m
use_of (m.attribute1)
# `m` is a shorter prefix, but you could as well
# define a more meaningful name after the context

Factorized version

If you don't mind about importing multiple entity all at once in a batch, you can:

from package.subpackage.module import attribute1, attribute2
# and etc.

Not in my first favorite taste (I prefer to have one import statement per imported entity), but may be the one I will personally favor.

Update (2012-09-14):

Finally appears to be OK in practice, except with a comment about the layout. Instead of the above, I used:

from package.subpackage.module import (

    attribute1, 
    attribute2,
    attribute3,
    ...)  # and etc.
How are things going when you write "from . import module" into "/package/subpackage/__init__.py"?
Your "factorized version" seems exactly right for what you want to do. If you do a separate import line for attribute1 and attribute2 (as you "prefer"), you're just deliberately giving yourself more work. There's no reason to do that.
Sorry but I don't get what you want. Could you rephrase your question in a more clear manner? What would you like to do exactly? I mean, what would you like to write that does not work and how would you expect it to work? By what I read I think you what the semantics of the import to be like Java's or maybe C's include. Last thing: you can make a module "star-import" safe adding an __all__ variable that contains a list of the names that should be exported when star-imported. edit: Okay, reading BrenBarn answer I understood what you meant.

B
BrenBarn

You seem to be misunderstanding how import searches for modules. When you use an import statement it always searches the actual module path (and/or sys.modules); it doesn't make use of module objects in the local namespace that exist because of previous imports. When you do:

import package.subpackage.module
from package.subpackage import module
from module import attribute1

The second line looks for a package called package.subpackage and imports module from that package. This line has no effect on the third line. The third line just looks for a module called module and doesn't find one. It doesn't "re-use" the object called module that you got from the line above.

In other words from someModule import ... doesn't mean "from the module called someModule that I imported earlier..." it means "from the module named someModule that you find on sys.path...". There is no way to "incrementally" build up a module's path by importing the packages that lead to it. You always have to refer to the entire module name when importing.

It's not clear what you're trying to achieve. If you only want to import the particular object attribute1, just do from package.subpackage.module import attribute1 and be done with it. You need never worry about the long package.subpackage.module once you've imported the name you want from it.

If you do want to have access to the module to access other names later, then you can do from package.subpackage import module and, as you've seen you can then do module.attribute1 and so on as much as you like.

If you want both --- that is, if you want attribute1 directly accessible and you want module accessible, just do both of the above:

from package.subpackage import module
from package.subpackage.module import attribute1
attribute1 # works
module.someOtherAttribute # also works

If you don't like typing package.subpackage even twice, you can just manually create a local reference to attribute1:

from package.subpackage import module
attribute1 = module.attribute1
attribute1 # works
module.someOtherAttribute #also works

Your comments goes in the same direction as the ones from Ignacio Vazquez-Abrams (I've commented his message). You addition at the end, about using module.attribute1 is something I though about, but I though there would be a way to avoid the necessity of a prefix everywhere. So I have to either use a prefix every where, or creating a local alias, repeating the name. Not the style I was expecting, but if there's no way (after all, I'm used to Ada, which requires something similar with its renaming declarations).
@Hibou57: It's still not clear to me what you're trying to accomplish in your "Version 2". What do you want to do that isn't possible? You want to never retype any part of the package/module/attribute name, but still import both the module and its attribute?
I wanted to have a local package reference, just like the way you can have a local object reference. Seems there are finally really local module reference, but you can't import from these. It's a mix of local and global with a funny taste (some stuffs can be local, some others must be global, I don't like it, but I'm OK as long as I now better understand how it works). Thanks for your message by the way.
I'm not sure you still understand how it works. Or that you did at 2012 in any case.
Every time I get back to Python after a 6-month layoff, I end up here. If only I could upvote each time I visit this page! I'm going to make a gigantic poster with this sentence: "There is no way to "incrementally" build up a module's path by importing the packages that lead to it."
I
Ignacio Vazquez-Abrams

The reason #2 fails is because sys.modules['module'] does not exist (the import routine has its own scope, and cannot see the module local name), and there's no module module or package on-disk. Note that you can separate multiple imported names by commas.

from package.subpackage.module import attribute1, attribute2, attribute3

Also:

from package.subpackage import module
print module.attribute1

Your reference to sys.modules['name'] which I didn't know until now, made me think that's what I was afraid (and BrenBarn confirms): there's nothing like real sub‑packages in Python. sys.modules, as its name suggest, is global, and if all reference to modules rely on this, then there is nothing like a local reference to a module (may be to come with Python 3.x?).
Your use of "reference" there is ambiguous; the first import in #2 generates a local reference to package.subpackage.module bound to module.
Yes, but that's a “module” I can't import from ;-)
T
Thomas Vander Stichele

If all you're trying to do is to get attribute1 in your global namespace, version 3 seems just fine. Why is it overkill prefix ?

In version 2, instead of

from module import attribute1

you can do

attribute1 = module.attribute1

attribute1 = module.attribute1 is just repeating the name with no added value. I know it works, but I don't like this style (which does not implies I don't like your reply).
I guess I, like all people commenting here, do not understand what you do want to do. In all examples you give, it looks like you want to end up with a symbol from a subpackage in your namespace. Your non-working example (example 2) wants to do it by importing a submodule from a package, and then importing a symbol from that submodule. I don't know why you want to do it in two steps instead of one. Maybe explain more what your ideal solution would be and why.