背景

随着构建的Python项目越来越大,回顾以往项目代码,有些结构比较混乱,最近做的RAG项目流程较多,这里结合一些优秀开源项目,梳理一下项目架构,并对本次项目中出现的一些疑问做记录。

这里的参考项目偏AI方向, 主要参考如下2个项目架构:
24k star的国内开源RAG项目 Langchain-Chatchat
48k star的国外开源项目 privateGPT

组织架构

从上述两个项目中抽取其核心架构,项目构建的基础架构如下所示,其中,加粗的代表文件夹
own_project
├── configs
├── docs
├── main_folder
├── tests
├── scrips
├── CHANGELOG.md
├── README.md
├── requirements.txt
├── startup.py
├── LICENSE
整体来看,可以总结如下几点注意事项:

  1. 项目详情可以写在README.md中。
  2. 项目更新日志可以写在CHANGELOG.md中(项目小的话,可以在README中写关键更新,不需要特意维护更新详情)。
  3. 如果项目大,可以在docs中维护文档(privateGPT专门维护了一个docs的网页,使用Fern维护)。
  4. 在configs中写全部的配置。
  5. 主题代码文件可以在own_project新建一个main_folder(privateGPT是这种方式),也可以直接在own_project下写项目主要代码(chatchat是这种方式)。
  6. 根目录下包括项目的入口文件startup.py或者run.sh
  7. 运行的脚本文件写在scrips文件夹中

init.py文件详解

由于整个项目层级结构复杂,在完成项目的架构搭建后, 需要使用__init__.py对引入的包做简化处理。这里简单介绍下使用方法(这里忽略python2中的用法)
通俗来说,init.py 可以将文件封装成包,将多个文件合并到一个逻辑命名空间。
当在文件夹里创建__init__.py文件能够使该文件夹变成一个Module,当这个文件夹(Module)被import时,会先执行__init__.py里的代码。
__ init__.py里面一般写该文件夹Module里的子Module的需要具体import的属性,函数,类等。
假设我们的项目只有最简单的configs文件夹及project文件夹, 项目结构如下所示:
project
├── configs
**│ ├── **__init__.py
**│ ├── **conf.py
**├── run.py
conf.py中定义一个变量**TOP_K**
,**现在我们想在run.py中要引用conf.py中的变量
如果没有__ init__.py,当我们要导入一个py文件中的属性、函数、变量、类……时,需要使用如下方式:

1
2
3
4
5
6
from configs.conf import TOPK
print(TOP_K)

## 或者采用
from configs import basic
print(basic.TOP_K)

而 **不能 **采用更简洁的方式导入,例如:

1
2
3
4
import configs
print(configs.basic.TOP_K)
## 或者
print(configs.TOP_K)

这是因为configs是一个包(package),而不是一个模块(module)。包和模块的区别为:
模块(Module):python中一个**.py文件就是一个模块。模块是一个包含Python代码的文件,它可以定义函数、类和变量。其他Python程序可以通过import**语句导入模块,并使用模块中定义的内容。
包(Package):包是一个包含多个模块的目录(文件夹),是一种更高级别的组织方式,用于组织多个相关的模块。
通过使用包,可以更好地组织和管理项目中的代码,避免模块名称冲突,并提高代码的可维护性和可重用性。
如果在configs下的__init__.py文件中写入

1
from .basic import TOP_K

则在run.py中便可以使用如下方式直接导入变量

1
2
from configs import TOP_K
print(TOP_K)

所以在__Init__.py文件中通常写该文件夹Module里的子Module的需要具体import的属性,函数,类等

from 【.子Module文件名】import 子Module里的属性,函数,类等

测试函数

python的测试函数一般写在tests文件夹下,与代码目录在同一父路径下,这里需要使用python -m 指令运行tests文件夹下的测试文件。
python -m tests.test:这种方式会以模块的形式运行指定的测试模块, 确保 Python 解释器能够正确地找到测试模块,并且可以避免由于当前工作目录不同而导致的模块导入错误。
极简项目架构如下所示:
project
├── configs
**│ ├── **init.py
**│ ├── **conf.py
├── tests
**│ ├── **test.py
├── src
**│ ├── **show_def.py
我们需要在tests/test.py中对src/show_def.py中的函数做测试。
show_def.py内容如下:

1
2
3
4
from configs import TOP_K

def simple():
print(f"src show_def func {TOP_K}")

test.py内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from src import show_def


def test_show_def():
show_def.simple()


def main():
"""main"""
test_show_def()


if __name__ == "__main__":
main()

在shell中运行python -m tests.test,最终的输出结果为: src show_def func 5
可以看到,在show_def中成功导入了configs内的变量,且在test.py中成功对函数simple进行了测试。

参考:

python init.py 文件的用法
关于Python项目文件夹里建立__init__.py的理解