模块与包
模块 : 一个 .py 文件就是一个模块(module)
包: init.py 文件所在目录就是包(package)
当然,这只是极简版的概念。实际上包是一种特殊的模块,而任何定义了 path 属性的模块都被当做包。只不过,咱们日常使用中并不需要知道这些。
import
import 有两种形式:
-
import … 后面只能是模块或包
-
from … import … 中,from 后面只能是模块或包,import 后面可以是任何变量
import 的搜索路径
假设现在有foo.py模块,import时的搜索路径如下:
- 搜索「内置模块」(built-in module)
- 搜索 sys.path 中的路径
而 sys.path 在初始化时,又会按照顺序添加以下路径:
- foo.py 所在目录(如果是软链接,那么是真正的 foo.py 所在目录)或当前目录;
- 环境变量 PYTHONPATH中列出的目录(类似环境变量 PATH,由用户定义,默认为空);
- site(site-packages) 模块被 import 时添加的路径1(site 会在运行时被自动 import)。
-m参数
python -m [module]
-m参数主要是将模块当做脚本来运行,该模块的详细路径可以不用给定,使用-m参数之后python会像import一样去搜索该模块,然后将模块像脚本一样的直接运行。如果只给定包名,那么该包下需要有__main__.py模块
-m参数跟定的module需要去掉.py后缀
相对导入
from . import 从当前目录
from .. import 从上级目录
from ... import 从上上级目录
相对导入一般用于具有包结构的代码中,用来保证包中的模块在相互引用时不会受到其他包的影响
常见错误
当直接运行有包含有相对导入的模块时,就会出现以下错误:
ValueError: Attempted relative import in non-package
原因在于模块当成一个单独的脚本来运行,认为该模块不属于任何包,所以此时相对 import 就会报错。也就是说,无论命令行是怎么样的,运行时 import 的语义都统一了,不会再出现运行结果不一致的情况。
当含有相对导入的时候,路径需要有相对引入的最顶层的路径
Example
code/
|__ __init__.py
|__test/
| |__ __init__.py
| |__foo.py
|__temp/
| |__ __init__.py
| |__bar.py
|__top.py
foo.py中的代码:
from ..temp import bar
top.py中的代码:
import test.foo
首先直接运行foo.py,会抛出上面说到的异常,此时foo.py的路径没有包含code。 接着直接运行top.py,发现并不会报错,这是因为运行top.py时code路径已经包含了,所以foo.py在相对导入的时候就可以找到上级目录了。
绝对导入
- 所有的 import … 都是绝对 import
- 所有的 from XXX import … 都是绝对 import
当我们要运行包里的一个模块时,例如我们要运行上面code包中的test子包中的foo.py模块,此时相对导入不管有,除了用-m参数之外,还可以手动将code的绝对路径加到foo.py模块中的sys.path中来:
import sys
sys.path.append('/../code')
两种导入方式的差异
首先,绝对 import 是 Python 默认的 import 方式,其原因有两点:
- 绝对 import 比相对 import 使用更频繁
- 绝对 import 能实现相对 import 的所有功能
其次,两者搜索模块的方式不一样:
-
对于相对 import,通过查看 name 变量,在「包层级」(package hierarchy)中搜索 例如上面code包中,每个模块对应的名为: top.py:top foo.py:test.foo bar.py:temp.bar 所以在执行top.py的时候,foo.py中通过__name__(temp.bar)找到了bar
-
对于绝对 import,当不处于包层级中时,搜索 sys.path
import 的大致过程
import 的实际过程十分复杂,不过其大致过程可以简化为:
if module_name in sys.modules:
return sys.modules[module_name]
else:
module_path = find(module_name)
if module_path:
module = load(module_path)
sys.modules[module_name] = module
return module
else:
raise ImportError
sys.modules 用于缓存,避免重复 import 带来的开销;load 会将模块执行一次,类似于直接运行。
模块删除与重载
使用del [module]可以将某个import的模块从当前模块中删除,那么就不能引用到该模块了
由于重复 import 只会执行第一次 import,可以使用reload [module]可以再次import某个模块,相当于再运行一次该模块,也可以在模块代码改变后,reload将重新导入修改过的模块。类似于热更新。
可以通过dir()查看当前import过的module。
Tips
- import 会生成 .pyc 文件,.pyc 文件的执行速度不比 .py 快,但是加载速度更快
- 重复 import 只会执行第一次 import
- 如果在 ipython 中 import 的模块发生改动,需要通过 reload 函数重新加载
- import * 会导入除了以 _ 开头的所有变量,但是如果定义了 all,那么会导入 all 中列出的东西