1. module 与 package 的概念
一般会把 module 对应为一个 py 文件,package 对应为一个文件夹。但是这其实是一种不严谨的说法。module 准确说是 python 里对模块的一种抽象,它是 object,可以包含子 object。至于在操作系统层面表现为什么,其实都可以。
一般认为 package 表现为定义了 init 的文件夹,但是这也不准确。准确说 package 是有__path__属性的 module。在 python3 的定义里,无论有没有定义 init,文件夹都可以作为 package。
文件/文件夹是操作系统的概念,而 module 与 package 是抽象的概念。import 的作用就是将各类文件导入为 python 可用的模块
2. import 找文件的顺序
当我们做 import test 的时候发生了如下这些事,首先它会拿到 test 这个字符串,把它作为名字来寻找这个 module
(1)它首先会检查缓存去看看有没有叫做 test 的 module 已经被读取进来了,如果有的话就不用下面这些 load 的过程可以直接把它复制给 test
(2)如果没有它就要寻找这个名字叫做 test 的 module。在寻找的过程中,它首先会去看这个名字是不是一个 built in module。所谓的 built in module 就是 Python 自带的那些 module
比如说 Sys 对吧 OSMAT 这些 module。
(3)如果它不是 built in module 的话它就会在几个文件夹内寻找可以被 load 成 test 的文件最常见的就是名字叫做 test. py 的文件。那它会在哪些文件夹内寻找这个路径被保存到了 sys. path 里。我们可以通过打印 sys. path 来观察它会在哪些文件夹内寻找这个 module 。
如果我们是以 Python example. py 这种形式运行的一个脚本的话,这个 sys. path 的第一个一般是我们这个文件所在的文件夹。同时在 Python 运行的时候它会往里放一些 Python 自带的一些 package 比如 async. io multiprocessing 的位置,还有这个 site packages 就是你 pip install 的位置。
那在 Python 的运行时你是可以手动改 sys. path 的也就是你可以手动控制在 import 的时候 Python 在哪些位置寻找这个 module 这里注意的是它会根据 sys. path 按顺序寻找,而一旦找到了它就不再找了。
3. 导入模块的过程
比如说我们现在 test. py 里面建立了一个 class A ,那在导入时 python 就会新建一个 module,然后在这个 module 里面定义一个 class A 。在完成 module object 之后它会更新一下缓存这样未来如果有其他的文件其他的代码再 import 这个名字的时候就不用再 load 一次了。最后它会把 module object 复制给叫做 test 的变量,也就是说在做完 import test 之后 test 就可以作为一个变量被使用了。我们打印它一下,它里面保存的就是 module test。我们也可以打印一下 test. A ,就像我们说的 test 是一个自己的命名空间。我们在里面定义了 A 这个 class 那 test. A 就是我们定义的 class A。
当我们做 import test 的时候,我们实际上是根据 test 这个名字拿到了一个 module 。再把 module 保存到了名字叫 test 的变量里。
也就是说这里的 test 实际上是有两个责任的。我能不能把这两个责任分开,我还是想通过 test 找到 module ,但是我想把它保存到另外一个变量名里。这个时候我们就可以用 import as 。它的含义就是根据 test 这个名字去找 module 然后把 module 保存到 t 这个变量里我们看如果我们打印 t 它打印的还是 module test 这个 module 本身还是 test 而如果我们 print (test) 就会发现找不到了因为 test 这个变量并没有被定义。
还有的时候我们可能对 module 本身不感兴趣,我们只需要 module 里面的某一个 object。 module 是很多 python object 的容器。比如说我只想要 test module 里面我们刚才定义的 class A,这个时候我们可以做 from test import a ,在我们做这件事的时候我们依然会 load test 这个 module ,依然会刷新缓存,只不过我们不再会把 module 复制给任何一个变量,而是在 module 里面找到这个名字为 a 的 variable,把变量 a 里面保存的 object 再复制到我当前 module 下的变量 a 。我们可以看到我们 example. py 里面的变量 a 里面保存的就是 test 里面 class A 了。同样的刚才那个 a 也是兼具了两个责任我们依然可以把这个责任拆开,这样我 import 的依然是 test 里面的 class A 但是这个 class A 在 example. py 这个文件里它的变量名就变成了 as 指定的名字。
4. 导入 package 的过程
接下来我们说一下 package ,我们依然是在当前文件夹下,我们有一个文件夹叫做 mypackage。我们可以通过 import mypackage 来把这个 package 给 import 进来,那么刚才已经说过 package 就是一种特殊的 module,所以 import package 的过程跟 import module 是非常非常相似的。唯一的一个小小的区别,我们刚才在 import module 的时候,我们把这个文件里面的程序在一个单独的命名空间里面运行了一下然后构成了 module。而当我们尝试 import 一个 package 的时候它会查看 package 文件夹下有没有 init 这个文件,如果没有的话它就不会运行任何额外的代码,如果有的话它就会运行 init 这个文件。
现在我这个 mypackage 文件夹下并没有 init 文件,我这个 import 也是可以正常运行的。那我们可以在 mypackage 文件夹下增加一个 init. py 文件,然后在这个文件里面我们打印一个 mypackage is imported 。这个时候我们再运行这段程序就可以看到 mypackage is imported 被打印出来了。那在我们 import 一个 package 的时候实际上是在一个单独的命名空间里运行 init 这个文件,然后用这个命名空间来构成一个 module ,当然这个 module 它是特殊的 module 也就是 package。
5. 导入 package. module 和直接导入 package 的区别
比如说如果我们 init. py 里面有一个 class b 那我们在 import 这个 package 之后就可以拿到 mypackage. b 。这里注意一下当我们 import 一个 package 的时候它只会运行这个 package 文件夹下的 init. py 这个文件,也就是说尽管 mypackage 文件夹下有一个 mymodule. py 但是当我们 import 这个 package 的时候 Python 是不知道这件事的。我们可以查看一下这个 import 进来的 mypackage 你可以看到 b 在里面,但是 mymodule 并不在。
而如果想 import 一个 package 下的 module 我们就需要通过这种形式来 import ,就是 package. module 。当我们在做这种 import package. module 或者是 package. subpackage. module 的时候它的原理和刚才 import module 或者 import package 是非常相似的。这个 mypackage. mymodule 也会被当做一个 string 作为 module 的 identifier。然后 Python 会根据这个字符串去寻找我去哪来 import 这个 module 。稍有不同的是当你尝试这样 import 的时候它会把每一个 package 都 import 进来。也就是当你做 import mypackage. mymodule 的时候,它的后台会 load 一下 mypackage,并且更新换存。其次当你 import mymodule 的时候它还会在 mypackage 里面增加一个属性 mymodule 来指向 mymodule 。刚才在我们只 import mypackage 的时候是没有办法打印出来 mypackage. mymodule 的。但是如果我们以这种方式 import 就可以找到它了。如果我们打印它里面的属性的话你也能看到 mypackage 里面是有 mymodule 这个属性的。
这种带点的形式还有一个不同就是它会把这个东西复制给一个变量。如果像我们现在这样是 import mypackage. mymodule 它会把最外面的那一层 package 复制给 package 名字的变量。也就是说当我们做完 import 之后我们会出现一个全局变量 mypackage 指向这个 package 。大家可以看到我们是能打印出来 mypackage。之所以这么设计是可以让你直观的直接去使用 mypackage. mymodule 来拿到这个 module 。但是如果我们像刚才那样尝试使用 as 的话它是会把最尾端的 module 给复制到 as 后面这个变量上的。大家可以看到 import mypackage. mymodule as m,我们打印 m 这个变量的时候这个变量里保存的是 mypackage. mymodule 这个 module,同时 mypackage 这个变量也并不存在了。
6. 相对引用 relative import
我们刚才讲的所有的 import 方式都叫做 absolute import 。有的时候会需要在一个 package 里的不同 module 之间相互引用,而这些 module 之间的相对关系是更容易被发现或者说更稳定的。因为比如说这个 package 可能会改名字,或者说这个 package 可能会被放到其他的 package 里面,都会导致这个 module 的绝对路径被改变。这个时候 relative import 就起到了作用,我们看 mymodule 和 util 这两个文件是在同一个 package 下的,所以我们这里可以写成 from . util import f 。就是在我这个 module 所在的 package 里面,找叫做 util 的 module ,这样就解决了我们之前提到的那些问题。
很多人在理解这个 relative import ,都会出现一些问题。每一个 relative import 都是先找到它的绝对路径,然后再 import 的。它会通过 module 的 package 这个变量去计算绝对路径。我们看我们在 example. py 里面 import mypackage. mymodule 然后打印 module 的 package 它就会告诉你它的 package 叫做 mypackage 。因此在这里面你尝试做 from. util 的时候它就会变成 from mypackage. util. import f ,它是会把这个 relative import 给转换成 absolute import 的。
也正因如此,如果一个文件使用了相对引用。而我们直接去运行这个文件的话,它会告诉你这个 relative import 出错了。因为当你直接通过 py 去运行这个文件的时候这个文件会被当做 main module load 进来,它并不属于任何一个 package 。于是它的 relative import 就没有办法成功的转换成 absolute import 。换言之 relative import 只能在 package 里面的 module 中使用并且这个 module 在被导入的时候一定是要跟着 package 一起被导入的。有的时候我们想导入的 module 跟我不在同一个文件夹而是在上一个文件夹这个时候我们就用两个点代表往上走一个 package,这个 package 下面有一个这样的 module 。那实际上理解 relative import 其实很简单你只需要意识到 Python 在真正运行的时候是必须要把这个 relative import 给变成 absolute import 然后再运行就好理解了在这个过程中它需要知道自己属于哪个 package 而当你直接运行这个文件的时候它是完全不知道自己是属于哪个 package 的。