【Python】my SOP of Python 编写包 (package)

My SOP of Python 包编程

背景

前几天转发了一篇 Python类、模块、包的区别 重在理清我正准备要将一个有 500 行核心代码的 .py 文件拆分出来,是称为拆分成包,还是叫模块。

当然,看了博文之后,了解到,实际上在python 中,我们将 .py 文件不作为 __main__ 运行,实际上就是当作 “模块” 使用了。也就是说,一个 .py 文件对应的是一个模块(module),一个 dir/ 文件夹,对应的就是一个 包 (package)1

在我拆分一个 .py 文件成包的时候,出现了一点儿问题,所以有必要总结一下。当然,做成 SOP,照做就行,因为有些东西我们无需深究,应当把精力放在我们需要关注的地方,而非这类 编程gong语言ju 的细枝末节。

1 SOP

为了能够使示例根据自释性,我就直接使用明显的命名了。

          all_in.py  <========================<-  +--------------------------+
            |                                  \  | many import              |
            \/                                    |                          |
pro-root/                                         | GLOBAL_VARS              |
    |---- package/                                |                          |
    |        |-------- __ini__.py                 | def utils_func(): ...    |
    |        |-------- utils.py                   |                          |
    |        |-------- functional1_module.py      | class GRAND():...        |
    |        |-------- functional2_module.py      |                          |
    |         `------- others.py                  | class PARENT(GRAND):..   |
    |---- setup.py                                | def main(argc, argv):... |
    |---- LICENCE, README, etc...                 |if __name__=='__main__':  |
     `--- main.py                                 +--------------------------+

package/ 内的文件内容基本上是这样的:

#########################################################################
# package/utils.py     |  # main.py
GLOBAL_VARS            |  from package.utils import *
                       |  from package.functional1_module import GRAND
def utils_func():...   |  from package.functional2_module import PARENT
                       |  def main(argc, argv):
                       |       ...
                       |  if __name__ == '__main__':
                       |      main(len(sys.argv), sys.argv)
                       |
##########################################################################
# package/functional1_module.py  | # package/functional2_module.py
from .utils import *             | from .utils import *
import ...                       | from .functional1_mdule import GRAND
                                 | 
class GRAND():                   | class PARENT(GRAND):
    ...                          |     ....
                                 |
###########################################################################

注意到上面有一些差异:

  1. main.py 使用的是 from package.functional1_module import GRAND 来导入;
    functional2_module.py 使用的是 from .functional1_module import GRAND 来导入。

    functional2_module.py 导入的是本包内的功能模块,所以使用相对导入,这样做是有原因的:

    在 package/ 未来 $ python setup.py install 的时候,这样不用修改包内文件的任何内容
    严格要求自己在 package/ 开发的时候,就不要将开发测试写在 package/functional1_module.py 这样的文件里面了。要写在上层的 main.py 文件中。
    main.py 不使用相对导入,package/ 在 install 之后它也不用调整内容。
    from . import module (from .module import class) 使用相对导入

  2. 相对导入知识点:
    Python-相对导入作用域

2 Note

2.1 递归导入(向前引用原则)陷阱注意

这里就跟 C 语言的 头文件要注意使用 #ifndef __XX_H #define __XX_H ... #endif 类似地要考虑这种问题。
但是在 python 中,原因有所不同。

C 语言中,不像上述那样处理头文件的话,在递归导入的情况中会显示重复定义。
而 python 中的递归导入,往往是“没有定义”。

我是从书上学习来的,书上讲述地也很好理解了,我不觉得自己能解释地更清楚,所以针对这个问题,我就直接将 Learning Python 上相关的知识点和解释贴出来,以供参考。

向前引用原则2:
Leaning Python CH24 - 顶层代码的语句次序重要性(续)Leaning Python CH24 - 顶层代码的语句次序重要性
递归导入的陷阱3
Leaning Python CH24 - 递归式的 from 导入无法工作(续)Leaning Python CH24 - 递归式的 from 导入无法工作

最后一点补充

“因为导入会从头到尾执行一个文件的语句”

如果对这句话有不清楚的地方,实际上 Learning Python 一书对此有单独的一节解释和示例。
只不过这个特性也就是比较显而易见了,在你需要了解到自己制作 “包” 这个问题上的时候,即使你不清楚上述这句话的概念,但是也应该注意到过这个现象了。所以我不会对这个内容多做说明。

有一个值得一提的是,上面我的示例中 package/utils.py 中的 GLOBAL_VARS 就是使用此特性,因此其它模块在有必要使用这类全局变量的时候,可以通过 from .utils import GLOBAL_VARS 这样的语句使用来自 utils.py 中的全局变量(严格意义上,这里是希望它作为一个常量)。


关于 Python 的“常量“ 也是一个值得有所了解的话题,但是并非本文主题;众所周知,元组是个定义不可变变量好内置类型。
但是如果你想给不可变常量带上一个名称,而非使用 ALL_CONST[0], ALL_CONST[1] 这样的方式来使用常量,那么标准库的 collections.namedtuple 是个好的 solution。4

3 2019/03/06 更新

在写了更多的 Python 代码,创建/实践更复杂的 Python 项目之后,终于理解了 Python 中属于项目一部分的模块文件该如何处理了!

首先假设有一个 Python 项目,整个项目文件夹在如下位置:

$ pwd
/home/jos/project

即,这里整个项目文件夹,我为了方便就称之为 project

那么如果你使用:

$ python
>>> import sys
>>> sys.path
['', ......, ......, .......]

你可以自己运行一下试试

注意到开始第一个 ''。这说明说,“当前的目录”已经属于 Python 解释器导入包的时候索引路径的其中之一。

3.1 顶层文件导入模块与包内模块相对导入

也就是说,如果项目文件夹内部的文件像这样:

project/
 |---- main.py
 |---- cli.py
 |---- package1/
 |       |---- __init__.py
 |       `---- module_a.py
 `---- module_2.py

顶层文件直接导入模块/包

那么在 main.py 中可以这么直接使用:

import cli

import package1
from package1.module_a import Class_I

import module_2
from module_2 import Class_II

...

以上用法都是没有问题的。

包内模块使用相对导入

package1 中,因为这个定位是一个“包”,作为一个包,我们总是希望它是提供一个尽可能独立的功能。所以最好是它只依赖它自己本身的代码。
package1 文件夹中的文件可以使用相对导入来导入属于这个包本身范围的 py 文件:

#### this is pacake1/module_a.py file
from . import *

上面 line 2 代码会导入 package1/__init__.py 中所有(全局)变量和函数和类。

当然,也有相反的做法,__init__.py 导入 module_a.py
也就是说,如果还有 module_b.py 等,也被 __init__.py 导入,由 __init__.py 统一提供该 package1 提供的功能。
区别只是在于“作者”如何设计而已,都是可行的。
但是尽最大可能不要相互导入。

3.2 项目内模块导入模块(ex: utils.py) - 待补充。


Reference


  1. Learning Python - CH23 模块和包 ↩︎

  2. Learning Python - CH24 高级模块话题 > 顶层代码的语句次序重要性 ↩︎

  3. Learning Python - CH24 高级模块话题 > 递归式的 from 导入无法工作 ↩︎

  4. 流畅的 Python - CH2 - 2.3.4 具名元组 ↩︎

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值