Richard Hansen
Richard Hansen

Reputation: 54163

relative and absolute imports with the same name causing "AttributeError: 'module' object has no attribute ..."

With the following code:

I get the following error:

$ ./foo.py
Traceback (most recent call last):
  File "./foo.py", line 3, in <module>
    package.go()
  File "/tmp/test/package/__init__.py", line 5, in go
    config(level=logging.DEBUG)
AttributeError: 'module' object has no attribute 'DEBUG'

It is as if the from .logging import config line is importing .logging as logging even though I don't want that.

If I swap the import lines in package/__init__.py so that it looks like this:

from __future__ import absolute_import
from .logging import config
import logging
def go():
    config(level=logging.DEBUG)
    logging.getLogger().debug('this is a test')

then it works:

$ ./foo.py
DEBUG:root:this is a test

What's wrong with the original version? Or is this a bug in Python 2.7.8?

Upvotes: 2

Views: 625

Answers (1)

Martijn Pieters
Martijn Pieters

Reputation: 1122182

Modules inside packages, once imported, are set as attributes on their parent package.

The moment you import .logging, it is added to the package namespace, e.g. in the globals() dictionary of __init__ in the package directory.

By swapping the imports, you then replaced the logging global in the __init__ module namespace with the top-level package again; first the sub-module was imported and added to the globals, then the logging package was imported, replacing global.

Demo:

$ mkdir package
$ cat > package/__init__.py << EOF
> from __future__ import absolute_import
> from .logging import foo
> print globals().keys()
> print logging
> import logging
> print globals().keys()
> print logging
> EOF
$ cat > package/logging.py << EOF
> foo = 'bar'
> print 'imported package.logging'
> EOF
$ python -c 'import package'
imported package.logging
['logging', '__builtins__', '__file__', 'absolute_import', '__package__', '__path__', '__name__', 'foo', '__doc__']
<module 'package.logging' from 'package/logging.pyc'>
['logging', '__builtins__', '__file__', 'absolute_import', '__package__', '__path__', '__name__', 'foo', '__doc__']
<module 'logging' from '/usr/local/Cellar/python/2.7.9/Frameworks/Python.framework/Versions/2.7/lib/python2.7/logging/__init__.py'>

Right after importing foo from the .logging package, the logging global shows up. Importing the top-level logging package then rebinds the name again.

Upvotes: 3

Related Questions