Python 导入相关
1 简介
Python
导入相关的问题说简单也简单, 说坑也坑。
如果平时使用的时候避开容易出现坑的地方, 那么 import
就会老老实实的, 没有任何问题。
但是, 常在河边走, 哪有不湿鞋。
为了尽可能的弄清楚这些坑, 决定来总结一下。
2 import 语法
使用 import
导入的东西只有三种: 包, 模块, 其他对象.
- 包:
__init__.py
文件所在目录 - 模块: 单个
.py
文件 - 其他对象: 除了 包 和 模块 以外的 Python Object
使用 import 的两种格式:
import package/module
from package/module import object
单独使用 import
的时候, 导入的只能是 包 或 模块.
而 from ... import ...
的形式则可以从 包 或 模块 中导入任意对象。
3 相对导入和绝对导入
3.1 相对导入
相对导入的方式只能用在 包 内部, 语法格式为:
from . import object
.
代表当前目录, ..
代表父级目录, 之后每多一个点代表目录上升一层。
很明显, 相对导入的含义是很明确的, 出现的坑也比较少。
需要注意的一点: 代表相对路径的符号 .
只能出现在 from
后面而不能出现在 import
后面, 即:
from . import utils # True import .utils # False
3.2 绝对导入
绝对导入可以在任何地方使用, 所有非 相对导入 都是绝对导入。
绝对导入根据 sys.path
来查找 包 或 模块.
sys.path
是一个列表, 通常情况下会包含
- 内建模块 的索引路径
- 第三方库 的保存路径
- 环境变量
PYTHONPATH
列出的目录 - 脚本所在目录或当前目录
前三个路径一般不需要怎么管, 容易出现问题的地方是第四个 脚本所在目录或当前目录.
首先, 需要清楚的一点是: 这个路径会被添加到 sys.path
最前面, 所以如果该路径下存在和内建或第三方库同名的模块或包, 使用绝对导入会优先导入那个同名的模块。
然后, 问题就是确定添加的这个目录到底是 脚本所在目录 还是 当前目录 了。
这个问题通过实践来测试:
pkg/ +--- __init__.py +--- test.py
test.py
中的内容为:
# -*- coding: utf-8 -*- import sys print(sys.path[0:1])
执行结果:
version | python -m pkg.test | python pkg/test.py |
---|---|---|
Python2.7 |
[''] |
['C:\\Users\\Administrator\\Desktop\\pkg'] |
Python3.6 |
[''] |
['C:\\Users\\Administrator\\Desktop\\pkg'] |
对于直接执行脚本的情况, 添加的目录都是 脚本所在目录.
而使用 Python -m
的时候, 添加的目录都是一个 空字符串.
现在可以来测试一下 空字符串 代表的含义:
Desktop/ +--- string/ +--- __init__.py +--- pkg/ +--- __init__.py +--- test.py +--- string.py
test.py
的内容:
# -*- coding: utf-8 -*- import string print(string)
执行结果:
version | python -m pkg.test | python pkg/test.py |
---|---|---|
Python2.7 |
<module 'pkg.string' from 'pkg\string.py'> |
<module 'string' from 'C:\Users\Administrator\Desktop\pkg\string.pyc'> |
Python3.6 |
<module 'string' from 'C:\\Users\\Administrator\\Desktop\\string\\__init__.py'> |
<module 'string' from 'C:\\Users\\Administrator\\Desktop\\pkg\\string.py'> |
可以看到, Python pkg/test.py
的输出结果是一样的, 而 Python -m
的结果有所区别。
对于 Python2.7
来说, 空字符串 代表的目录仍是脚本所在目录, 而对于 Python3.6
, 代表的是当前目录。
简单汇总一下:
- 直接执行脚本 时添加的路径是 脚本所在目录.
- 使用
Python -m
的方式执行脚本时,Python2
添加的是 脚本所在目录, 而Python3
添加的是 当前目录.
另外, 绝对导入 会根据 名称 来进行导入, 而在导入时, 包内模块的 名称 就是 包名.模块名, 因此在包内可以使用下面的方式来进行导入:
from package.module import obejct
这也是在相对简单的包结构中 PEP8 推荐使用的方式, 而复杂的包结构可能会有多层嵌套的 包, 此时如果还用 绝对导入 就是 包A.包B.包C.模块 的形式了, 此时使用 相对导入 会更加简洁.
PS: __init__.py
的 名称 就是 包名.
4 同名的包和模块
前面了解到, 当前目录下的 包 和 模块 的导入优先级最高。
那么, 要是当前目录下存在名字相同的 包 和 模块 怎么处理呢 ?
尝试一下:
pkg/ +--- string/ +--- __init__.py +--- __init__.py +--- test.py +--- string.py
test.py
的内容:
# -*- coding: utf-8 -*- import string print(string)
执行结果:
version | Python pkg/test.py |
---|---|
Python2.7 |
<module 'string' from 'C:\Users\Administrator\Desktop\pkg\string\__init__.pyc'> |
Python3.6 |
<module 'string' from 'C:\\Users\\Administrator\\Desktop\\pkg\\string\\__init__.py'> |
可以看到, 对应 Python2.7
和 Python3.6
来说, 结果是相同的。
即: 同一目录下, 同名的 包 和 模块, 包 的导入优先级比 模块 高。
可以得出的一个优先级排序为:
- 当前目录或脚本所在目录 ==> 内建模块 ==> 第三方库
- 包 ==> 模块
5 执行一次导入后
很多时候, 我们对某个模块或模块内的对象会进行不止一次的导入。
比如对于模块: base
, ma,
mb
和 mc
.
模块 ma
, mb
和 mc
都导入了模块 base
中的对象, 此时, 这三个模块中导入的对象是什么情况呢 ?
这个我进行了一次尝试, 详细的过程就不列出来了, 这里简单说一下结论:
- 当模块
base
被导入一次后, 其内部的 对象 在全局中就只存在 唯一 的一个实例 - 在 第一次 导入之后的导入, 导入的对象都是那个 唯一 的实例
- 此时, 如果在某一个模块中对实例进行了修改, 那么这个修改会反馈到其他地方
上面的结论对于大多数情况都是适用的, 但是存在一种例外:
from module import num
像这样直接从 模块 导入一个 数值 对象, 这个数值对象就会绑定到 导入它 的模块, 并从新在内存中创建一个实例。
此时对于这个 数值 的修改不会影响到其他地方。
但是, 对于其他的 引用 对象以及像 module.num
这样进行的修改会影响到其他地方。
6 其他使用细节
import
会生成.pyc
文件,.pyc
文件的执行速度不比.py
快, 但是加载速度更快- 重复
import
只会执行第一次import
- 如果在 执行过程中 中
import
的模块发生改动,可以通过reload
函数重新加载 from xxx import *
会导入除了以_
开头的所有变量,但是如果定义了__all__
, 那么会导入__all__
中列出的东西import xxx.object
的方式不能直接使用object
, 仍然需要通过xxx.object
的方式使用import xxx.object as obj
的方式可以直接使用obj
Python3.4
之后的版本中, 内置函数reload
被移除了, 要使用可以通过from importlib import reload
导入还可以这样导入:
from click import ( argument, command, echo, edit, group, Group, option, pass_context, Option, version_option, BadParameter, )