UP | HOME

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 是一个列表, 通常情况下会包含

  1. 内建模块 的索引路径
  2. 第三方库 的保存路径
  3. 环境变量 PYTHONPATH 列出的目录
  4. 脚本所在目录或当前目录

前三个路径一般不需要怎么管, 容易出现问题的地方是第四个 脚本所在目录或当前目录.

首先, 需要清楚的一点是: 这个路径会被添加到 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.7Python3.6 来说, 结果是相同的。

即: 同一目录下, 同名的 模块, 的导入优先级比 模块 高。

可以得出的一个优先级排序为:

  • 当前目录或脚本所在目录 ==> 内建模块 ==> 第三方库
  • ==> 模块

5 执行一次导入后

很多时候, 我们对某个模块或模块内的对象会进行不止一次的导入。

比如对于模块: base, ma, mbmc.

模块 ma, mbmc 都导入了模块 base 中的对象, 此时, 这三个模块中导入的对象是什么情况呢 ?

这个我进行了一次尝试, 详细的过程就不列出来了, 这里简单说一下结论:

  1. 当模块 base 被导入一次后, 其内部的 对象 在全局中就只存在 唯一 的一个实例
  2. 第一次 导入之后的导入, 导入的对象都是那个 唯一 的实例
  3. 此时, 如果在某一个模块中对实例进行了修改, 那么这个修改会反馈到其他地方

上面的结论对于大多数情况都是适用的, 但是存在一种例外:

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,
    )
    

7 参考链接

版权声明:本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可