【Python】python中_init_.py 到底有啥用?

每当你尝试从其他文件夹导入代码时,都会输入一个空的__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结合使用,可以更有效地处理自定义配置。

033d45dee35e105f42a42786a3b9790b.gif

 
 

3fe5490472ea7ee3b062d25688e51f4d.jpeg

 
 
 
 
 
 
 
 
 
 
 
 
往期精彩回顾




适合初学者入门人工智能的路线及资料下载(图文+视频)机器学习入门系列下载机器学习及深度学习笔记等资料打印《统计学习方法》的代码复现专辑
  • 交流群

欢迎加入机器学习爱好者微信群一起和同行交流,目前有机器学习交流群、博士群、博士申报交流、CV、NLP等微信群,请扫描下面的微信号加群,备注:”昵称-学校/公司-研究方向“,例如:”张小明-浙大-CV“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~(也可以加入机器学习交流qq群772479961)

4f93a60ea191d0326d6ee92fa1166f7a.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
__init__.pyPython有一些限制和用法,具体如下: 1. __init__.py文件是一个特殊的文件,它用于将一个目录作为Python包进行导入。当一个目录被作为包导入时,Python会自动查找并执行该目录下的__init__.py文件。 2. __init__.py文件可以为空,但通常会包含一些初始化代码或者导入其他模块的语句。这些代码会在导入包时执行。 3. __init__.py文件可以包含任意Python代码,但是应该避免在其做过多的事情,以免导入包时产生额外的开销。 4. __init__.py文件的代码只会在第一次导入包时执行一次,后续导入同一个包时不会再次执行。 5. __init__.py文件可以用来定义包级别的变量、函数和类,这些定义可以在包的其他模块使用。 6. __init__.py文件还可以用来控制包的导入行为,例如在__init__.py使用\_\_all\_\_变量来指定导入时的可见性。 下面是一个示例,展示了一个包的结构和__init__.py文件的用法: ``` my_package/ __init__.py module1.py module2.py ``` __init__.py文件的代码可以是这样的: ```python # 导入其他模块 from .module1 import some_function from .module2 import MyClass # 定义包级别的变量 PI = 3.14159 # 定义包级别的函数 def some_other_function(): pass ``` 这样,在导入my_package时,__init__.py文件的代码会被执行,可以使用my_package.some_function()和my_package.MyClass来访问模块的函数和类。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值