python中_init_.py 到底有啥用?

教育   2024-08-24 18:30   四川  


每当你尝试从其他文件夹导入代码时,都会输入一个空的__init__.py。它几乎已经成为大多数 Python 开发人员(无论是初学者还是高手)的肌肉记忆。但我们真的知道吗__init__.py

在这篇博文中,云朵君将和大家一起深入了解__init__.py非空的工作原理以及它如何以三种方式__init__.py帮助我们开发 Python 程序的。

什么是__init__.py

__init__.py是一个 Python 文件,它告诉 Python 解释器该文件夹应该被视为一个包。

与 C 和 C++ 等编译语言不同,在使用之前必须预编译依赖项,而 Python 的解释器会即时获取依赖项。要向 Python 发出信号,告知某个文件夹包含将在其他地方使用的代码,可以通过__init__.py__init__.py将你的文件夹变成可导入的 Python 包。

在 Python 中创建一个类时,你通常还需要创建一个__init__函数。这将定义如何构造对象,并且是创建类的对象时首先要执行的操作。类似地,__init__.py是 Python 包的构造函数。每当导入包时,它都会首先执行。空__init__.py表示__init__Python 包的构造函数方法为空。这很好,但这并不意味着我们不能用它做更多的事情。

谨慎使用 __init__.py

由于__init__.py是 Python 包的构造函数,因此我们需要谨慎放置__init__.py的位置。

如果我们有一个名为的文件夹datetime,其中有一些用于处理日期格式的自定义实用程序函数:

# ./datetime/utils.py 
def increment_date(date: int, increment: int) -> int:
    """以毫秒为单位创建时间戳"""
    return date + increment

然后我们添加一个__init__.py,这样就可以在main.py中导入代码:

myfolder

├── datetime
│   ├── __init__.py
│   └── utils.py
└── main.py

# main.py

from datetime.utils import increment_date
from datetime import datetime

def main():
    timestamp = datetime.timestamp(datetime(2024, 2, 27))
    print(increment_date(timestamp, increment=10))

if __name__ == "__main__":
    main()

如果运行main.py会发生什么?

ImportError: cannot import 'datetime' from 'datetime'

通常,Python 解释器将按照(1)本地目录、(2)标准库和(3)已安装的 Python 模块的顺序对包发现过程进行优先排序。

通过将__init__.py放入名为datetime的文件夹,我们覆盖了名为的 Python 标准库datetime,导致我们的导入语句datetime.datetime失败。

避免此问题仅需要一个简单的修复:不要将__init__.py放在与其他 Python 标准库或已安装的 Python 模块同名的任何文件夹下。

了解了它的工作原理后__init__.py,可以用它做一些更奇特的事情!

定义Package级别配置

想象一下,如果代码中的所有 Python 文件都共享类似的配置:日志记录级别、常量、环境变量等。你不必将其设置在包中每个 Python 文件的顶部,而是可以将它们全部包含在__init__.py.

# myfolder/__init__.py 

import os 
import logs 

# 为整个包加载环境变量
OPENAI_API_KEY = os.getenv( "OPENAI_API_KEY" , None ) 

# 设置包级别常量
MODEL = "gpt-4" 

# 你还可以设置日志配置。
logging.basicConfig(level=logging.INFO)

# 添加可在整个包中方便重用的自定义装饰器
def foo(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        logging.info(f"Calling function: {func.__name__}")
        result = func(*args, **kwargs)
        logging.info(f"Function {func.__name__} completed")
        return result
    return wrapper

在你的代码库中,你可以执行以下操作:

#myfolder/bar.py 

#从你的包__init__.py 相对导入来自
from . import OPENAI_API_KEY, MODEL, foo

from openai import OpenAI
from typing import Optional

client = OpenAI()

@foo
def chat_with_openai(prompt: str, llm: Optional[str] = None) -> Any:
    """Send a prompt to an LLM and return the response"""
    llm = llm or MODEL
    
    return client.chat.completions.create(
              model=llm,
              messages=[
                {"role""system""content""You are a helpful assistant."},
                {"role""user""content": prompt}
              ]
            )

基于上述内容,你还可以执行以下操作:

  • 功能标记
  • 高级记录器配置(例如,将所有日志保存到文件夹)
  • 设置默认参数(例如,如果未提供输出目录,则保存到 X)
  • 使用情况监控(例如,将所有函数调用发送到你的云实例的自定义装饰器)
  • 主题定制/定义(例如语言区域、明/暗主题)
  • 使用自定义装饰器集中处理错误

简化包导入

随着代码库变得越来越复杂,你将添加更多类和函数。按照经验法则将代码分成内聚单元,你最终可能会得到以下结果:

foo

├── llm_email_responder
│   ├── __init__.py
│   ├── base_email_responder.py
│   ├── mail_chimp_responder.py
│   ├── zoho_mail_responder.py
│   └── send_grid_responder.py
└── __init__.py

如果想使用 MailChimp 的 LLM 电子邮件回复器:

from foo.llm_email_responder.mail_chimp_responder import MailChimpResponder

最终得到了多行看起来非常相似的导入语句,此时需要记住代码库的内部结构。

__init__.py可用于管理导入并简化开发人员体验,同时屏蔽代码库中更私密/较低级别的部分。

# ./foo/llm_email_responder/__init__.py 

# 跳过最低级别的基本电子邮件回复器,
# 因为用户不需要像使用更高级别的实现那样频繁地使用它。
from .mail_chimp_responder import MailChimpResponder 
from .zoho_mail_responder import ZohoResponder 
from .send_grid_responder import SendGridResponder 

__all__ = [ 
    "MailChimpResponder" , 
    "ZohoResponder" , 
    "SendGridResponder" , 
]

通过上述操作,你现在可以__init__.py访问MailChimpResponder

from foo.llm_email_responder import MailChimpResponder

虽然这是一种在开发人员友好界面中公开低级代码的便捷方法,但它也会增加维护工作量,因为对低级代码库的任何更改都必须与__init__.py 的更改相匹配。我们还必须评估设计模式,以确保公开低级代码库形成一个有凝聚力的架构。

Langchain 经常使用这个技巧来确保我们不需要记住错综复杂的内部代码结构。

单例模式

你还可以使用__init__.py 强制实施单例设计模式。这将强制整个包使用在 __init__.py 中实例化的相同实例。

如果你的软件包需要建立与远程服务的连接、加载大型数据集或任何需要繁重工作量的先决条件,那么这可能会非常方便。

假设我需要连接到 Gemini Pro 来处理我处理财务报告的所有 LLM 需求。

foo 
│ 
├──report_analysisr 
│ ├── __init__.py 
│ ├──outline_extraction.py 
│ └──entity_extraction.py 
└── __init__.py

# .foo/report_analyser/outline_extraction 
from langchain_google_vertexai import VertexAI 

def  extract_outline ( page: str , **kwargs ) -> str : 
    llm = VertexAI( 
        model_name= "gemini-pro" , location= "europe-west2" , **kwargs 
    ) 
    prompt_template = """...<你的提示模板在此>..."""
     prompt = prompt_template.format_prompt(page=page) 
    return llm.invoke(prompt)
# .foo/report_analyser/entity_extraction 
from langchain_google_vertexai import VertexAI 

def  extract_entities ( page: str , **kwargs ) -> str : 
    llm = VertexAI( 
        model_name= "gemini-pro" , location= "europe-west2" , **kwargs 
    ) 
    prompt_template = """...<你的提示模板在此>..."""
     prompt = prompt_template.format_prompt(page=page) 
    return llm.invoke(prompt)
# .foo/report_analyser/entity_extraction 
from foo.report_analyser.outline_extraction import extract_outline 
from foo.report_analyser.entity_extraction import extract_entities 

def  main (): 
    page = "此处为文档的长页" 

    # 将创建与 VertexAI 的连接以提取大纲
    outline = extract_outline(page,temperature= 0.2 ) 

    # 将创建与 VertexAI 的另一个连接以提取实体
    entities = extract_entities(page,temperature= 0.2 )

每次运行操作时,代码库都必须与 VertexAI 建立新连接,从而产生不必要的网络开销。如果需要对 Gemini Pro 使用相同的配置,你可以改为执行以下操作:

# .foo/report_analyser/__init__.py
from langchain_google_vertexai import VertexAI

DEFAULT_LLM = VertexAI(
    model_name="gemini-pro",
    location="europe-west2",
    temperature=0.2,
)
# .foo/report_analyser/outline_extraction
from . import DEFAULT_LLM

from typing import Dict

def extract_outline(
    page: str,
    custom_llm_parameters: Optional[Dict] = None
) -> str:
    
    if custom_llm_parameters is None:
        llm = DEFAULT_LLM
    else:
        llm = VertexAI(
            model_name="gemini-pro",
            location="europe-west2",
            **custom_llm_parameters
        )
    prompt_template = """...<你的提示模板在此>..."""
    prompt = prompt_template.format_prompt(page=page)
    return llm.invoke(prompt)

如果你想要更好的代码配置,可以将创建 LLM 连接的部分拆分为单独的__init__.py文件,本质上是创建 LLM 连接管理器。将其与@lru_cache结合使用,可以更有效地处理自定义配置。


🏴‍☠️宝藏级🏴‍☠️ 原创公众号『数据STUDIO』内容超级硬核。公众号以Python为核心语言,垂直于数据科学领域,包括可戳👉 PythonMySQL数据分析数据可视化机器学习与数据挖掘爬虫 等,从入门到进阶!

长按👇关注- 数据STUDIO -设为星标,干货速递

数据STUDIO
点击领取《Python学习手册》,后台回复「福利」获取。『数据STUDIO』专注于数据科学原创文章分享,内容以 Python 为核心语言,涵盖机器学习、数据分析、可视化、MySQL等领域干货知识总结及实战项目。
 最新文章