Python conditional 'module object has no attribute' error with personal package distinct from circular import issue

前端 未结 2 1132
[愿得一人]
[愿得一人] 2021-02-19 11:27

I\'m getting a \'module object has no attribute ...\" error when trying to use a package heirarchy I created. The error is reminiscant of the error you get when there is a circ

2条回答
  •  面向向阳花
    2021-02-19 11:30

    Here's the why

    (This is, I believe, mostly supported by the explanation at http://docs.python.org/faq/programming.html#how-can-i-have-modules-that-mutually-import-each-other)

    When the Python interpreter encounters a line of the form import a.b.c, it runs through the following steps. In pseudo-python:

    for module in ['a', 'a.b', 'a.b.c']:
        if module not in sys.modules:
            sys.modules[module] = (A new empty module object)
            run every line of code in module # this may recursively call import
            add the module to its parent's namespace
    return module 'a'
    

    There are three important points here:

    1. The modules a, a.b, and a.b.c get imported in order, if they haven't been imported already

    2. A module does not exist in its parent's namespace until it has completely finished being imported. So module a does not have a b attribute until a.b has been imported completely.

    3. No matter how deep your module chain is, even if you import a.b.c.d.e.f.g, your code only gets one symbol added to its namespace: a.

      So when you later try to run a.b.c.d.e.f.g.some_function(), the interpreter has to traverse all the way down the chain of modules to get to that method.

    Here's what is happening

    Based on the code that you have posted, the problem seems to lie in the print statement in alpha/bravo/echo/__init__.py. What the interpreter has done by the time it gets there is roughly this:

    1. Set up an empty module object for alpha in sys.modules

    2. Run the code in alpha/__init__.py (Note that dir(alpha) won't contain 'bravo' at this point)

    3. Set up an empty module object for alpha.bravo in sys.modules

    4. Run the code in alpha/bravo/__init__.py:

      4.1 Set up an empty module object for alpha.bravo.charlie in sys.modules

      4.2 Run the code in alpha/bravo/charlie/__init__.py:

      4.2.1 Set up an empty module object for alpha/bravo/echo in sys.modules

      4.2.2 Run the code in alpha/bravo/echo/__init__.py:

      4.2.2.1 Set up an empty module object for alpha/bravo/delta in sys.modules

      4.2.2.2 Run the code in alpha/bravo/delta/__init__.py -- This finishes, so 'delta' is added to 'alpha.bravo's symbols.

      4.2.2.3 Add 'alpha' to echo's symbols. This is the last step in import alpha.bravo.delta.

    At this point, if we call dir() on all of the modules in sys.modules, we will see this:

    • 'alpha': ['__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__', '__return__'] (this is essentially empty)

    • 'alpha.bravo': ['__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__', 'delta'] (delta has finished being imported, so it's here)

    • 'alpha.bravo.charlie': ['__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__'] (empty)

    • 'alpha.bravo.delta': ['__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__', '__return__', 'delta.foo'] (This is the only one that has completed)

    • 'alpha.bravo.echo': ['__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__', '__return__', 'alpha']

    Now the intepreter continues with alpha/bravo/echo/__init__.py, where it encounters the line print alpha.bravo.delta.delta_foo(1). That starts this sequence:

    1. get the global variable alpha -- this returns the still-empty alpha module.
    2. call getattr(alpha, 'bravo') -- this fails, because alpha.bravo isn't finished being initialized yet, so bravo hasn't been inserted into alpha's symbol table.

    This is the same thing that happens during a circular import -- the module isn't finished being initialized, so the symbol table isn't completely updated, and attribute access fails.

    If you were to replace the offending line in echo/__init__.py with this:

    import sys
    sys.modules['alpha.bravo.delta'].delta_foo(1)
    

    That would probably work, since delta is completely initialized. But until bravo is complete (after echo and charlie return), the symbol table for alpha won't be updated, and you won't be able to access bravo through it.

    Also, as @Ric Poggi says, if you change the import line to

    from alpha.bravo.delta import delta_foo
    

    Then that will work. In this case, because from alpha.bravo.delta goes right to the sys.modules dict, rather than traversing from alpha to bravo to delta, it can get the function from the delta module and assign it to a local variable, which you can then access without any trouble.

提交回复
热议问题