python中的包和导入

python使用包(package)、模块(module)来组织代码,以便于维护和管理。
使用关键字import来导入模块。导入要指定搜索路径,即从何处搜索模块。
Python搜索模块路径是由四部分构成的:

  • 程序的主目录: 执行文件的目录,即__main__文件的目录。
  • PATHONPATH目录:环境变量,可以设置或扩展它。
  • 标准目录:DLLS, Lib, site-packages目录。
  • .pth文件的目录: 可以放在python安装目录或site-packages中。每行一个路径
    最后这4部分的路径都存储在sys.path列表中。
    因此也可以在代码中使用sys.path.add(“/path/to/module”)来添加自定义搜索路径。

导入基础

实验环境:python3.9.2 windows

1
2
import PACKAGE OR MODULE [as ALIAS]  
from PACKAGE OR MODULE import [MODULE or OBJECT] [as ALIAS]

考虑如下的结构:

在main中import sub时打印打sub:

1
<module 'sub' (namespace)> <class 'module'>

可见package也是一种module,只是标识为namespace了。

绝对导入和相对导入

  • 绝对导入:当从sys.path路径去搜索模块时,称为绝对导入。
    比如,import sysimport os
    执行sub_main.py,如果我们要导入上级目录的top.py,那么需要把top所在的目录添加到sys.path中。
1
2
3
4
5
6
7
# sub_main.py
import sys
import os
sys.path.append(os.path.abspath(os.path.dirname(__file__) + '/..'))
# sys.path.append('..') 依赖当前命令的执行目录,可以用os.getcwd()获取。
print(sys.path)
import top

绝对导入简单清晰明了。

  • 相对导入
    形如:
1
2
3
4
from . import x
from .. import x
from .m import x
from ...m import x

相对导入能避免一些包路径更改引入的大量绝对路径修改的麻烦。

相对导入有一个很重要的限制,就是相对路径不是一个顶层包,即__name__属性为__main__的顶层模块所在的目录

1
2
# main.py
import sub.sub2

main 导入sub2
sub2.py 有如下导入情境:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# sub2.py

## sub2 导入同包内的sub1
# import sub1 # error: No module named 'sub1'
import sub.sub1 # ok,绝对导入
from . import sub1 # ok, 相对导入模块
from .sub1 import add # ok, 相对导入模块内的函数
print(add(1, 2))

## 导入子包sub_sub
from .sub_sub import sub_sub # o, 相对导入

## 导入上层的top
# from .. import top # error: attempted relative import beyond top-level package
# 但是如果把整个python-import作为一个包,由另外的模块来引用。这样..就不是一个顶层包,
# from .. import top就是合法的

import top # ok, 绝对导入
print("module sub2")

__init__.py

当导入一个模块时(不管这个模块是包还是普通模块),包中的__init__.py文件会被自动导入。该文件可以用来做一些模块的初始化,控制import *的行为,简化外部导入,但有很多说法,只有目录下包含一个__init__.py文件才是一个包,我觉得是不对的。至少我自己使用中不是这样的。__init__.py只是一个会自动执行的普通py文件

考虑在main中导入sub_sub.py文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# main.py

# 当sub.__init__.py 中存在如下导入时:
# from .sub_sub import sub_sub
# from . import sub1
# 只需要导入sub就可以使用sub_sub和sub1。
# 否则,需要导入import sub.sub_sub.sub_sub和sub.sub1。
import sub

print(sub.sub1.os.getcwd())
print(sub.sub_sub.sub(1, 2))

# sub1.py
import os
print("module sub1")

def add(a, b):
return a + b

# sub.__init__.py

from .sub_sub import sub_sub
from . import sub1

print("sub __init__.py")

def div(a, b):
return a / b

# sub_sub/sub_sub.py
print('sub_sub: ', __file__)

def sub(a, b):
return a - b

import, importlib

import 关键字是__import__函数的语法糖,在动态导入中推荐使用importlib而不是直接使用__import__。

1
2
3
import importlib
a = importlib.import_module('sub.sub1') # 绝对导入
b = importlib.import_module('sub1', package='sub') # 相对导入
作者

BoostMerlin

发布于

2022-03-06

更新于

2023-04-16

许可协议