Pulpcode

捕获,搅碎,拼接,吞咽

0%

详解python import

之前写过一篇博客:从几个实验来分析python import,但这篇博客过于简陋,而且在工作中也发现,很多人都对此处知识存在很多盲区,所以这里想去写一篇比较专业的。

从哪开始找

当你import一个模块时,将通过以下路径,对模块进行搜索。

  1. 在当前目录下搜索该模块
  2. 在环境变量PYTHONPATH中指定的路径列表中依次搜索
  3. 在python的安装路径中搜索

如果你想看一下你python的包搜索路径,不妨使用如下命令:

1
2
import sys
print(sys.path)

那么,如果你想把某个路径添加到python的搜索路径怎么办呢?

1
PYTHONPATH=$PYTHONPATH:/xxx/xxx

注: /xxx/xxx就是你的路径。

package

module

在python中,一个包(package)其实就是一个目录,它里面可以有一个module,或者另一个包。

还有一个关键的东西,就是__init__.py,此文件的目的就是用来告诉python解释器将该目录当成一个内容包,即该目录是一个包,里面包含了python模块。

那么在python解释器执行的时候,会将模块一个一个的导入进来,放到sys.modules中,这是一个字典。

你可以打印sys.modules来一看究竟

从module到当前local

我先告诉你一件事情,你别看你在你的代码中写了import sys,但其实sys早就导入了。

我们的代码在import sys的时候,实际上不会重复再加载一次sys,而是将sys模块的名字引入到当前命名空间。

from future import absolute_import

如果你阅读过python源码,那么你可能经常会看见作者在开头写了这样一句话:from __future__ import absolute_import

字面意思感觉像是import此模块,就可以用“绝对的方式”进行导入了。但其实这就像tornado的@tornado.web.asynchronous命名一样,坑了不少人。

此句import的真实目的是禁用 implicit relative import, 但并不会禁掉 explicit relative import

那么它们有什么区别呢,首先我想告诉你,什么才是完整的包?
比如下面就是一个完整的包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
things
├── __init__.py
├── books
│   ├── __init__.py
│   ├── adventure.py
│   ├── history.py
│   ├── horror.py
│   └── lovestory.py
└── furniture
├── __init__.py
├── armchair.py
├── bench.py
├── screen.py
└── stool.py

那么如果你在stool中引用bench,有如下几种方式:

1
2
3
import bench # 此为implicit relative import
from . import bench # 此为 explicit relative import
from furniture import bench # 此为 absolute import

实际上只有第三种,才是官方推荐的,第一种是官方强烈不推荐的,python3中已经不可以使用了,我们的from future import absolute_import也就是为了禁用这种方式。

当然有些读者觉得,我即使加上这句话,还是可以导入,那是因为,我们讨论都是基于包内的,那么什么是包,首先,包内之间需要引用,而对于主控,应该是包外部的,由它来引用包。

所以你直接来两个文件,然后在a.py中直接import b来引用b.py,然后执行a.py没问题。这完全没有在我们的讨论的问题之内,所谓完整的包,包内和包外是有一个边界的。

比如你的main.py就是你的主控,它在你的main在things下,那么这个包并不能被成为完整的包。

两个错误

沿用上一节提到的包目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
things
├── __init__.py
├── books
│   ├── __init__.py
│   ├── adventure.py
│   ├── history.py
│   ├── horror.py
│   └── lovestory.py
└── furniture
├── __init__.py
├── armchair.py
├── bench.py
├── screen.py
└── stool.py

我们在stool.py中编写:

1
from .. books import horror

然后执行stool.py文件,就会报错:

1
2
3
4
Traceback (most recent call last):
File "stool.py", line 1, in <module>
from .. books import horror
ValueError: Attempted relative import in non-package

但是假如你把main放到things目录下,那么就会包这种错:

1
2
3
4
5
6
Traceback (most recent call last):
File "main.py", line 1, in <module>
from furniture import stool
File "/Users/liuaiqi/laboratory/package/things/furniture/stool.py", line 1, in <module>
from .. books import horror
ValueError: Attempted relative import beyond toplevel package

实际上就是说,如果你的主控在包中,那么你的包内相对引用就会出现问题,就像我在上一小节描述的“完整的包”

name

首先你在每个模块下都试着打印__name__,而如果你在包内使用相对引用, 你试着将主控放到不同的地方去引用这些包,你会发现,它们打印出来的结果是不一样的。

这是因为__name__来决定它在包结构中的位置

reloads

比如你现在在一个解释器中,你对一个源码进行修改,然后你想重新加载这个模块,这时候,你就需要reload。
reload实际上会擦出底层字典的内容,并通过重新执行模块的源代码来刷新它.

setdefaultencoding为什么要reloads?

在python2.x中,默认编码是ascii的,这也是人们在编写转码类代码时,会报错的原因:UnicodeDecodeError: 'ascii' codec can't decode byte ......

然后你就需要将这三行代码引进来。

1
2
3
import sys
reload(sys)
sys.setdefaultencoding('utf-8')

虽然我们知道reload的作用,但是为什么要reload?因为在python2.5以后,sys初始化之后,会删除sys.setdefaulting方法。