模块与包

在了解 import 之前,有两个概念必须提一下:

  • 模块: 一个 .py 文件就是一个模块(module)
  • 包: __init__.py 文件所在目录就是包(package)

当然,这只是极简版的概念。实际上包是一种特殊的模块,而任何定义了 __path__ 属性的模块都被当做包。只不过,咱们日常使用中并不需要知道这些。

两种形式的 import

import 有两种形式:

  • import ...
  • from ... import ...

两者有着很细微的区别,先看几行代码。

from string import ascii_lowercase
import string
import string.ascii_lowercase

运行后发现最后一行代码报错:ImportError: No module named ascii_lowercase,意思是:“找不到叫 ascii_lowercase 的模块”。第 1 行和第 3 行的区别只在于有没有 from,翻翻语法定义发现有这样的规则:

  • import ... 后面只能是模块或包
  • from ... import ... 中,from 后面只能是模块或包,import 后面可以是任何变量

可以简单的记成:第一个空只能填模块或包,第二个空填啥都行。

import 的搜索路径

提问,下面这几行代码的输出结果是多少?

import string
print(string.ascii_lowercase)

是小写字母吗?那可不一定,如果目录树是这样的:

./
├── foo.py
└── string.py

foo.py 所在目录有叫 string.py 的文件,结果就不确定了。因为你不知道 import string 到底是 import 了 ./string.py 还是标准库的 string。为了回答这个问题,我们得了解一下 import 是怎么找到模块的,这个过程比较简单,只有两个步骤:

  1. 搜索「内置模块」(built-in module)
  2. 搜索 sys.path 中的路径

而 sys.path 在初始化时,又会按照顺序添加以下路径:

  1. foo.py 所在目录(如果是软链接,那么是真正的 foo.py 所在目录)或当前目录
  2. 环境变量 PYTHONPATH中列出的目录(类似环境变量 PATH,由用户定义,默认为空);
  3. site 模块被 import 时添加的路径1site 会在运行时被自动 import)。

import site 所添加的路径一般是 XXX/site-packages(Ubuntu 上是 XXX/dist-packages),比如在我的机器上是 /usr/local/lib/python2.7/site-packages。同时,通过 pip 安装的包也是保存在这个目录下的。如果懒得记 sys.path的初始化过程,可以简单的认为 import 的查找顺序是:

  1. 内置模块
  2. .py 文件所在目录
  3. pip 或 easy_install 安装的包

相对 import 与 绝对 import

相对 import

当项目规模变大,代码复杂度上升的时候,我们通常会把一个一个的 .py 文件组织成一个包,让项目结构更加清晰。这时候 import 又会出现一些问题,比如:一个典型包的目录结构是这样的:

 string/
├── __init__.py
├── find.py
└── foo.py

如果 string/foo.py 的代码如下:

# string/foo.py
from string import find
print(find)

那么 python string/foo.py 的运行结果会是下面的哪一个呢?

  • <module 'string.find' from 'string/find.py'>
  • <function find at 0x123456789>

按我们前面讲的各种规则来推导,因为 foo.py 所在目录 string/ 没有 string 模块(即 string.py),所以 import 的是标准库的 string,答案是后者。不过,如果你把 foo 当成 string 包中的模块运行,即 python -m string.foo,会发现运行结果是前者。同样的语句,却有着两种不同的语义,这无疑加重了咱们的心智负担,总不能每次咱们调试包里的模块时,都去检查一下执行的命令是 python string/foo.py 还是 python -m string.foo 吧?

相对 import 就是专为解决「包内导入」(intra-package import)而出现的。它的使用也很简单,from 的后面跟个 . 就行:

# from string/ import find.py
from . import find
# from string/find.py import *
from .find import *

我们再看个复杂点的例子,有个包的目录结构长这样:

one/
├── __init__.py
├── foo.py
└── two/
├── __init__.py
├── bar.py
└── three/
├── __init__.py
├── dull.py
└── run.py
from . import dull
from .. import bar
from ... import foo
print('Go, go, go!')

改成

from .dull import *
from ..bar import *
from ...foo import *
print('Go, go, go!')

结果是一样的。

那么 python string/foo.py 和 python -m string.foo 的运行结果又是怎样呢?运行一下发现,两者的输出分别是:

Traceback (most recent call last):
File "string/foo.py", line 1, in <module>
from . import find
ValueError: Attempted relative import in non-package
<module 'string.find' from 'string/find.py'>

原因在于 python string/foo.py 把 foo.py 当成一个单独的脚本来运行,认为 foo.py 不属于任何包,所以此时相对 import 就会报错。也就是说,无论命令行是怎么样的,运行时 import 的语义都统一了,不会再出现运行结果不一致的情况。

最新文章

  1. easyui datagrid 悬浮事件
  2. 《Qt Quick 4小时入门》学习笔记3
  3. Ubuntu 16.04 风扇特别响解决办法
  4. 纯JS实现中国行政区域上下联动选择地址
  5. C语言小练习二
  6. poj1068 模拟
  7. 广义线性模型 GLM
  8. bat面试总结
  9. NFS网络操作系统介绍以及相关应用
  10. [改善Java代码] 提倡异常的封装
  11. DB2分区表删除和添加分区
  12. Ring对象
  13. Code First研究学习2_基本的错误及解决方法
  14. TextUtils
  15. 三星5.0以上机器最简单激活Xposed框架的经验
  16. Luogu3793 由乃救爷爷 分块、ST表
  17. 初级Java工程师面试所遇面试题
  18. java生成PDF,各种格式、样式、水印都有
  19. WP8.1学习系列(第二十六章)——控件模板
  20. golang json 编码解码

热门文章

  1. Redis(四)Jedis客户端
  2. django-URL重定向(八)
  3. Springboot与任务整合(四)
  4. 实战SpringCloud响应式微服务系列教程(第九章)使用Spring WebFlux构建响应式RESTful服务
  5. Fine-Grained(细粒度) Image – Papers, Codes and Datasets
  6. MySQL:数据库基本认识
  7. java常用类Time
  8. CentOS生产环境无网络安装percona-xtrabackup2.4【RPM安装教程】
  9. nginx篇最初级用法之三种虚拟主机基于域名\基于端口\基于IP地址端口的虚拟主机
  10. 如何实现一个MVVM