python 是通过module组织代码的,每一个module就是一个python文件,但是modules是通过 package 来组织的。

python package 的定义

package 的定义很简单,在当面目录下有 __init__.py 文件的目录即为一个 package 。

不管 __init__.py 是空的还是有内容的,这个目录都会被认为是一个 package ,这是一个标识

Python 的 package 以及 package 中的 __init__.py 共同决定了 package 中的 module 是如何被外界访问的。

__init__.py 内的导入顺序

这个在解决重写的问题时会很有用,探究过程就如这篇博客所说的,这里只放结论。

检查顺序如下:

  1. __init__.py 文件内变量
  2. 是不是 package 内的subpackage
  3. 是不是 package 内的module

__init__.py 的写法

这个文件本质上和我们平常写的python文件没有太大的区别,但是要注意两点:

  1. 保证 __init__.py 轻量化,最小化其中代码的副作用,最好全部打包成函数。
  2. 把本模块里面的公用的method在 __init__.py 的时候暴露出来,这样在其他地方的引用就不需要引用具体位置,只需要引用这个包就好了。

跳转链接: 使用自定义的 package

下面这个原先的版本应该是 python 2 的,python 3 完全不同了,所以进行了一些删减,仅仅作为小知识点。

到这里我们想到的第一件事情就是把我们的元素都导入,于是我们就想到使用:

1
from module import *

因为 * 实在是范围太广了,那么如何决定哪些是可以导入的呢?这里我们引入变量 __all__

__all____init__.py 文件中的一个列表型变量,通过定义 __all__ 我们可以规定 from module import * 可以导入的元素有哪些。我们只需要在 __init__.py 中添加一行代码:

1
__all__ = ["module0", "moduole1", "module2", ···]

之后Python会按顺序逐一导入列表中的元素。

这个就很直接了,我们不使用 * 就一个一个得导入。

导出将按照以下规则执行:

  • 此 package 被导入
  • 执行 __init__.py 中可被执行的代码
  • __init__.py 中定义的 variable 被导入
  • 显式导入的 module 被导入

所以也可以侧面反应我们的 __all__ 变量的作用是怎样的了。能够节省一定的时间。

在 class 已经足以解决大部分问题的当下,为什么需要 package ?

首先随着项目的变大,通过 package 来整理项目变得尤为重要。

Python 是通过 module 组织代码的,module 即一个 py 文件,module 又是通过 package 来组织的, package 是一个包含 __init__.py 的文件夹,代码,module, package 它们三者的关系就是:module 包含代码, package 至少包含一个为 __init__.py 的 module。

这中间没有 class 的概念,class 更多是面向对象的时候使用的,可以作为 module 中的一个元素。

使用自定义的 package

跳转链接:init.py 的写法

这里有一个盲区,我们在使用 package 的时候往往不会放在某一个特定的仓库,只有完全标准化之后的 package 才会放到某一个地方保存好。

问题就来了,我们的工作目录往往不在系统路径下,也就是说没有把当前工作路径添加到System Variable Path。我们无法直接调用同一目录下的 package 。

所以我们需要做的事情是添加一个临时的系统路径,在import之前添加上这个系统路径。

1
2
import sys
sys.path.append("upper folder of the package ")

这里我们需要把目标pakage的上层目录添加进path中,这样我们才能找到这个 package 。

如果我们的自定义 package 和 main.py 有相同的系统里路径,那就简单多了。

1
2
import sys, os
sys.path.append(os.getcwd())

然后我们有两种使用自定义 package 的方式如下所示。

这种就是比较直接的方法,我们在 main.py 中添加每一个 package 或者 subpackage 的路径,然后直接 import 进来。

代码举例:

1
2
3
4
5
6
7
# we assume that the DIY package is under cwd
import sys, os
sys.path.append(os.path.join(os.getcwd(), ' package 0', ' package 1'))
sys.path.append(os.path.join(os.getcwd(), ' package 0', ' package 2'))

import package 0. package 1.module0
import package 0. package 2.module1

这个如果要分类的话属于递归调用的方法,我们在每一层的 __init__.py 将路径临时添加进path中,保证了只要我们的最外层 package 的在path中了,我们剩下的 subpackage 都可以被索引到。

因此我们不需要再对我们的 main.py 做任何修改了。我们需要做的事情只有在每一个 __init__.py 中添加一层当前文件夹。

具体代码如下:

1
2
3
4
import sys, os
sys.path.append(os.path.join(os.getcwd(), 'subpackage '))

from subpackage import *

阅读 __init__.py 源码后发现大家写得都不太一样。但是相信基本思想就这些了,应该出入不大了。

小知识点

python中 r'string', b'string', u'string', f'string' 的含义:

字符 含义
r/R 非转义的原始字符串
b bytes
u/U 表示unicode字符串
f/format() 格式化操作

参考

  1. Python 基础教程之包和类的用法
  2. Python Modules and package s
  3. 彻底明白Python package 和模块
  4. 深入理解 Python package
  5. python3 模块和包