目录

Python的import你用对了吗

目录

写一个中大型项目, 往往需要在项目根路径下安排多层级的子目录和文件, 然后各文件之间通过import来相互连接.

各 python 文件之间如何通过import连接?

我们要遵从一个规范:

所有 python 文件都要以项目根目录为起始点来import, 当然除了目录下的__init__.py文件, 该文件一般是通过from . import module来使本目录和本目录下的module文件做关联.

import其他相关规范也需要注意:

  1. 所有import尽量放在代码文件的头部位置
  2. 每行import只导入一个对象
  3. 当我们使用from xxx import xxx时, import后面跟着的对象要是一个package(包对应代码目录)或者module(模块对应代码文件), 不要是一个类或者一个函数
  4. 标准模块、第三方模块、自定义模块要按这个先后顺序排列, 三类之间使用一个空行分隔.

比如项目根路径为:

/path/project

项目的组织结构:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
├── main.py
├── module.py
├── package1
│   ├── __init__.py
│   ├── module.py
│   └── sub_package
│       ├── __init__.py
│       └── module.py
└── package2
    ├── __init__.py
    └── module.py

项目文件内容:

main.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#!/usr/bin/env python3

import module
import package1
import package2

module.who()
package1.module.who()
package2.module.who()
package1.sub_package.module.who()
package1.module.call_projectroot_module()
package1.module.call_package2_module()

module.py

1
2
def who():
    print('I am projectroot.module.who()')

package1/module.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import module
import package2

def who():
    print('I am projectroot.package1.module.who()')

def call_projectroot_module():
    module.who()

def call_package2_module():
    package2.module.who()

package1/sub_package/module.py

1
2
def who():
    print('I am projectroot.package1.sub_package.module.who()')

package1/sub_package/__init__.py

1
from . import module

package1/__init__.py

1
2
from . import module
from . import sub_package

package2/module.py

1
2
def who():
    print('I am projectroot.package2.module.who().')

package2/__init__.py

1
from . import module

运行项目入口文件

1
python3 main.py

输出结果为:

1
2
3
4
5
6
I am projectroot.module.who()
I am projectroot.package1.module.who()
I am projectroot.package2.module.who().
I am projectroot.package1.sub_package.module.who()
I am projectroot.module.who()
I am projectroot.package2.module.who().

以上正确运行的约束条件为:

  • 运行项目的入口程序文件, 必须在项目的根路径下;
  • import的对象必须从项目根路径为起始点;
  • 如果import的对象为目录(package), 则在目录下必须有__init__.py文件, 且该文件中通过from . import module的形式引入该目录下相关模块, 这样才可以使用相关模块, 否则虽然import package时不会报错, 但使用package下面的模块时就会提示找不到相关模块;
  • 如果import的对象为目录.文件(package.module)这种形式, 目录下可以没有__init__.py文件;
  • 如果以from 目录 import 文件这种形式, 目录下也可以没有__init__.py文件;

值得解释的一个问题:

上面的 python3 环境并没有将项目根目录加入到sys.path中, sys.path指的是import时 python 的查找路径, 为什么子目录中的 python 文件从项目根路径为起始点import不会报错?

解答:

首先我们知道sys.path中存储的是import时 python 查找库的搜索路径, 通过打印该变量内容, 我们可知, ''空字符串是第一个路径, 这个空字符串代表的是当前 python 文件的所在目录, 不论是绝对路径执行还是相对路径执行, 这个值都是一样的, 都是当前 python 文件所在的目录.

然后再回答一下上面的问题, 因为我们是以项目根路径下的 python 文件作为项目的执行文件的, 该文件的import相当于把其他当前目录或各级子目录中的各个 python 文件中的代码(包括import代码)都加载到了当前文件中, 所以子目录文件中的import也是从项目根路径下开始的, 当然不会报错.

当然了, 我们要是单独执行子目录中的 python 文件, 就会报找不到模块的错误了.

还有一个问题, 以上我们部署到生成环境当然没问题, 问题是在项目开发过程中, 我们肯定是需要单独执行子目录中的 python 文件的, 这个时候改怎么配置呢?

解答:

把项目根目录加入到sys.path中, 可通过如下方式设置:

1
2
3
4
5
6
7
# 设置系统环境变量
export PYTHONPATH="/project/root/path"

# 如果是virtualenv环境, 加入到activate文件末尾即可
cat >>py37env/bin/activate <<EOF
export PYTHONPATH="/project/root/path"
EOF

这样子目录 python 文件在当前目录下找不到对象时, 就会去项目根路径下去找了.