Importing a dotted-name package locally for unit t

2019-08-10 22:41发布

问题:

I am trying to develop and test a package foo.bar. I have the following directory layout:

myproject
  setup.py (for package foo.bar)
  foo
    bar
      __init__.py
      ...
  tests
    main.py
    test_something.py

In test_something.py, I want to import the local copy of foo.bar ideally just using 'import foo.bar'.

In main.py I have:

import os
import sys

sys.path.append(
    os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

import test_something

However, I get an error:

File "./tests/main.py", line 22, in <module>
  import test_something
File "/myproject/tests/test_something.py", line 28, in <module>
  import foo.bar
ImportError: No module named bar

Is it possible to do this?

回答1:

If you want foo to be considered as a package, you need to have a __init__.py directly under foo directory, it can be empty, but the file needs to be there. Otherwise Python would not consider foo as a package, and you cannot do -

import foo.bar

As said in the comments -

In any event I don't want foo to be a package, I want foo.bar to be the package.

This is not directly possible, as in, you cannot do import foo.bar unless foo is a Python package (or a namespace package explained below).

The only other alternative here would be to add foo folder directly into sys.path using -

sys.path.append(
    os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'foo')))

And then you would have to import it as -

import bar

If you are using Python 3.3 + , then you can use namespace packages , and you can import foo.bar without having to define a __init__.py inside foo.

This is explained well in - PEP 0420 , According to the specification, the new laoding procedure for python packages/modules (from 3.3 +) is -

During import processing, the import machinery will continue to iterate over each directory in the parent path as it does in Python 3.2. While looking for a module or package named "foo", for each directory in the parent path:

  • If <directory>/foo/__init__.py is found, a regular package is imported and returned.
  • If not, but <directory>/foo.{py,pyc,so,pyd} is found, a module is imported and returned. The exact list of extension varies by platform and whether the -O flag is specified. The list here is representative.
  • If not, but <directory>/foo is found and is a directory, it is recorded and the scan continues with the next directory in the parent path.
  • Otherwise the scan continues with the next directory in the parent path.

If the scan completes without returning a module or package, and at least one directory was recorded, then a namespace package is created. The new namespace package:

  • Has a __path__ attribute set to an iterable of the path strings that were found and recorded during the scan.
  • Does not have a __file__ attribute.