在这篇文章中,我们将深入探讨 __init__.py 的工作原理,以及其对我们 Python 开发人员的三种帮助。
什么是 __init__.py ?
__init__.py 是一个 Python 文件,它告诉 Python 解释器文件夹应被视为一个软件包。
与 C 和 C++ 等编译语言不同的是,Python 的解释器在使用前必须预先编译依赖项,而 Python 的解释器会在运行中获取依赖项。通过 __init__.py 向 Python 发出信号,告诉它某个文件夹包含的代码将在其他地方使用。
因此,可以把 __init__.py 看作是一个看门人。它将你的文件夹变成一个可导入的 Python 包。
但是,__init__.py 的作用还不止于此。在 Python 中创建类时,通常也需要创建一个 __init__ 函数。它定义了对象的构造方法,是类对象创建时首先要执行的函数。同样,__init__.py 是 Python 包的构造函数。每当包被导入时,它会首先被执行。空的 __init__.py 意味着 Python 包的 __init__ 构造函数方法是空的。
谨慎使用 __init__.py
由于 __init__.py 是 Python 软件包的构造函数,所以我们需要谨慎地选择 __init__.py 的位置。
举个例子,如果我们有一个名为 datetime 的文件夹,其中有一些用于处理日期格式的自定义实用程序函数:
然后,我们添加一个 __init__.py 以便在 main.py 中导入代码:
如果运行 main.py 会发生什么?
通常,Python 解释器会按照 (1) 本地目录、(2) 标准库、(3) 已安装的 Python 模块的顺序优先执行软件包发现过程。
通过将 __init__.py 放在名为 datetime 的文件夹中,我们覆盖了名为 datetime 的 Python 标准库,导致导入 datetime.datetime 语句失败。
要避免这个问题,只需要一个微小的修正:确保不要将 __init__.py 放在与其他 Python 标准库或已安装的 Python 模块同名的文件夹下。
了解了__init__.py 的工作原理后,我们来看看可以用它做些什么!
1. Define Package Level Configurations1.定义软件包级配置
想象一下,如果代码中的所有 Python 文件都共享类似的配置:日志级别、常量、环境变量等。与其在软件包中每个 Python 文件的顶部进行设置,不如将它们都包含在 __init__.py 中。
在你的代码中,您可以这样做:
除此之外,你还可以做以下事情:
- 特征标记
- 高级日志配置(例如,将所有日志保存到文件夹中)
- 设置默认参数(例如,如果未提供输出目录,则保存至 X)
- 使用监控(例如,将所有函数调用发送到云实例的自定义装饰器)
- 主题定制/定义(如语言定位、浅色/深色主题)
- 使用自定义装饰器集中处理错误
2.简化软件包导入
随着代码库变得越来越复杂,您将添加更多的类和函数。例如,你可能会得到以下结果:
如果要为 MailChimp 使用 LLM 电子邮件应答器,你需要:
你很快就会发现,你最终会有多行看起来非常相似的导入语句,这对你和你记忆代码库的内部结构提出了挑战。
__init__.py可用于管理导入,简化开发人员的体验,同时屏蔽代码库中更私密/更低级的部分。
有了上面的 __init__.py 文件,现在就可以用它访问响应器了:
虽然这是一种在开发人员友好的界面上公开底层代码的便捷方法,但它也会增加维护工作量,因为底层代码库的任何更改都必须与 __init__.py 中的更改相匹配。我们还必须对设计模式进行评估,以确保公开底层代码库能形成一个内聚的架构。
单例模式
您还可以使用 __init__.py 强制执行单例设计模式。这将强制整个软件包使用在 __init__.py 中实例化的同一个实例。
如果您的软件包需要建立与远程服务的连接、加载大型数据集或任何需要繁重工作量的先决条件,这可能会非常方便。
比方说,我需要连接到 Gemini Pro 来处理我在处理财务报告方面的所有 LLM 需求。
每次运行操作时,您的代码库都必须与 VertexAI 创建新的连接,从而产生不必要的网络开销。如果您打算为 Gemini Pro 使用相同的配置,或许可以采用以下方法:
如果想要更好的代码配置,可以将从 __init__.py 创建 LLM 连接的部分拆分成一个单独的文件,本质上就是创建一个 LLM 连接管理器。将它与 @lru_cache 结合使用,你可能会发现处理自定义配置的效率更高。
希望你已经对 __init__.py 有了一些了解,并对如何更好地使用它有了一些想法。