ZetCode

Python __init__.py

最后修改日期:2025年3月25日

本指南将详细探讨Python的__init__.py文件,介绍其在包初始化、导入管理和高级用法中的作用。

基本包初始化

本节将介绍__init__.py在定义Python包中的基本作用。我们将通过一个最小化示例和一个包含包级变量的示例,来展示它如何建立一个包及其元数据。

空的 __init__.py

__init__.py最简单的用途是将一个目录标记为一个包。以下示例演示了这种基本设置。

textutils/__init__.py
"""Marks the directory as a Python package."""

这个空的__init__.py文件带有文档字符串,将textutils目录指定为一个包。它的存在允许Python识别该目录以便进行导入,如import textutils,从而确保跨Python版本的兼容性。

包级变量

__init__.py中添加变量可以定义包的元数据。本示例展示了如何设置和访问这些属性。

textutils/__init__.py
"""Text utility package initialization."""
__version__ = "1.2.0"
__author__ = "Jane Smith"
PACKAGE_NAME = "textutils"

__init__.pytextutils包定义了元数据属性。__version____author__等变量遵循PEP 8约定,而PACKAGE_NAME是一个自定义常量,用于标识。

main.py
import textutils

print(f"{textutils.PACKAGE_NAME} v{textutils.__version__}")
print(f"Created by: {textutils.__author__}")
# Output: textutils v1.2.0
#         Created by: Jane Smith

脚本导入textutils并访问其属性,显示格式化的元数据。这演示了包级变量如何提供对信息的编程访问,这对于版本控制和文档记录非常有用。

导入控制

在此,我们将探讨__init__.py如何管理导入以简化包的API。示例包括暴露模块函数和控制通配符导入。

模块暴露

本示例展示了如何直接在包命名空间中暴露模块函数,从而提高可用性。

textutils/__init__.py
"""Text utility package initialization."""
from .formatting import to_uppercase, to_lowercase

__init__.pyformatting模块中的两个函数导入到textutils命名空间中。相对导入(.)确保了跨不同包安装的可移植性。

textutils/formatting.py
"""Text formatting utilities."""
def to_uppercase(text):
    return text.upper()

def to_lowercase(text):
    return text.lower()

formatting.py模块定义了to_uppercaseto_lowercase,它们会修改文本的大小写。这些函数通过__init__.py提供,用于直接的包级访问。

main.py
from textutils import to_uppercase, to_lowercase

print(to_uppercase("hello"))  # Output: HELLO
print(to_lowercase("WORLD"))  # Output: world

脚本直接从textutils导入并使用暴露的函数,演示了简化的访问。这种方法隐藏了内部模块结构,增强了封装性和可用性。

__all__ 用于通配符导入

通配符导入可以通过__all__进行控制。本示例定义了一组特定的公开名称。

textutils/__init__.py
"""Text utility package initialization."""
__all__ = ['to_uppercase', 'reverse_text']

from .formatting import to_uppercase
from .transform import reverse_text

__init__.py使用__all__来指定在通配符导入时导出哪些名称。它将to_uppercasereverse_text从单独的模块拉入包命名空间。

textutils/transform.py
"""Text transformation utilities."""
def reverse_text(text):
    return text[::-1]

transform.py模块定义了reverse_text,它使用切片反转字符串。该函数通过__init__.py包含在包的公共API中。

main.py
from textutils import *

print(to_uppercase("test"))    # Output: TEST
print(reverse_text("python"))  # Output: nohtyp

脚本使用通配符导入仅访问__all__中列出的名称。这确保了可控且可预测的API,防止了意外导入并保持了清晰性。

包初始化代码

本节演示了__init__.py如何在导入时执行代码。示例包括设置缓存和延迟加载子模块。

一次性初始化

以下示例设置了一个在导入时运行的包级缓存。

cachepkg/__init__.py
"""Cache package initialization."""
print("Initializing cachepkg...")

_cache = {}

def init_cache(key, value):
    """Initialize cache with a key-value pair."""
    global _cache
    _cache[key] = value

def get_cache(key):
    """Retrieve value from cache by key."""
    return _cache.get(key, "Key not found")

__init__.py在导入时打印一条消息,并设置一个私有的_cache字典。它定义了管理缓存的函数,该缓存会在包的生命周期内持续存在。

main.py
import cachepkg

cachepkg.init_cache("user", "Alice")
print(cachepkg.get_cache("user"))  # Output: Alice
print(cachepkg.get_cache("age"))   # Output: Key not found

脚本使用键值对初始化缓存并检索值,展示了缓存的功能。缺少键时的默认返回值提高了可用性和错误处理能力。

延迟加载

延迟加载会推迟子模块的导入,直到需要时才进行。本示例通过一个统计实用程序来实现这一点。

dataprocess/__init__.py
"""Data processing package initialization."""
def __getattr__(name):
    """Lazy load heavy submodules."""
    if name == 'stats':
        import dataprocess.stats as stats
        globals()['stats'] = stats
        return stats
    raise AttributeError(f"No attribute '{name}' in {__name__}")

__init__.py使用__getattr__仅在访问时加载stats模块。它将模块缓存到globals()中,以防止重复导入。

dataprocess/stats.py
"""Statistical utilities."""
def average(numbers):
    return sum(numbers) / len(numbers) if numbers else 0

stats.py模块定义了一个average函数,用于计算列表的平均值,并包含对空列表的检查以避免错误。

main.py
import dataprocess

print(dataprocess.stats.average([1, 2, 3]))  # Output: 2.0

脚本延迟加载stats模块,仅在需要时触发其导入。这展示了在保持功能的同时提高了导入性能。

高级模式

__init__.py的高级用法支持复杂的包设计。我们将介绍聚合子包和动态导入。

子包聚合

本示例将多个子包中的函数聚合到一个命名空间中,以方便用户。

utils/__init__.py
"""Utility package initialization."""
from .text import to_title
from .math import square
from .data import unique_list

__all__ = ['to_title', 'square', 'unique_list']

__init__.py从三个子包导入函数并在__all__中定义它们。它创建了一个统一的API,简化了对各种实用程序的可访问性。

utils/text.py
"""Text utilities."""
def to_title(text):
    return text.title()

text.py模块提供了to_title,它将字符串中的每个单词大写,为包提供文本格式化实用程序。

utils/math.py
"""Math utilities."""
def square(number):
    return number ** 2

math.py模块定义了square,它计算数字的平方,为包添加了数学功能。

utils/data.py
"""Data utilities."""
def unique_list(items):
    return list(dict.fromkeys(items))

data.py模块实现了unique_list,它会从列表中删除重复项,同时保留顺序,使用字典来提高效率。

main.py
from utils import to_title, square, unique_list

print(to_title("hello"))         # Output: Hello
print(square(4))                # Output: 16
print(unique_list([1, 2, 2, 3])) # Output: [1, 2, 3]

脚本导入并使用聚合的函数,展示了它们如何无缝协同工作。这种模式在提供便捷的顶级接口的同时,保持了模块化。

动态导入

动态导入允许在运行时加载模块。本示例实现了一个插件系统。

plugins/__init__.py
"""Plugin package initialization."""
import importlib

def load_plugin(name):
    """Load a plugin module dynamically."""
    try:
        return importlib.import_module(f"plugins.{name}")
    except ImportError as e:
        raise ImportError(f"Failed to load plugin '{name}': {e}")

__init__.py定义了load_plugin,它使用importlib动态导入模块。它包含错误处理,可以优雅地处理导入失败。

plugins/log.py
"""Logging plugin."""
def log(message):
    print(f"[LOG] {message}")

log.py模块提供了一个log函数,该函数在消息前加上“[LOG]”,作为日志记录功能的简单插件。

main.py
from plugins import load_plugin

log_plugin = load_plugin("log")
log_plugin.log("Starting")  # Output: [LOG] Starting

脚本动态加载log插件并使用其函数,演示了运行时可扩展性。这种方法非常适合基于插件的架构。

命名空间包

命名空间包可以跨越多个目录。本示例显示了__init__.py如何明确支持这一点。

nsutils/__init__.py
"""Namespace utility package."""
__path__ = __import__('pkgutil').extend_path(__path__, __name__)

__init__.py使用pkgutil.extend_path来扩展包的路径,从而实现命名空间包的行为。它支持将包拆分到不同目录中,这对于分布式开发很有用。

性能注意事项

优化__init__.py可以提高效率。这里的示例侧重于推迟重导入和验证需求。

繁重初始化

本示例推迟了繁重的导入,以提高初始包加载时间。

compute/__init__.py
"""Compute package initialization."""
def process_data(data):
    """Process data with deferred heavy import."""
    from .heavy import complex_calc
    return complex_calc(data)

__init__.py定义了process_data,它仅在调用时导入繁重的模块。这种推迟大大减少了包的初始导入时间。

compute/heavy.py
"""Heavy computation module."""
def complex_calc(data):
    return sum(x ** 2 for x in data)

heavy.py模块提供了complex_calc,它计算列表中平方值的总和,模拟资源密集型操作。

main.py
import compute

print(compute.process_data([1, 2, 3]))  # Output: 14

脚本调用process_data,触发对heavy的延迟导入。输出(1² + 2² + 3² = 14)显示计算有效,同时优化了启动性能。

导入时验证

在导入时验证需求可以防止后续的错误。本示例检查Python版本和平台。

validatepkg/__init__.py
"""Validation package initialization."""
import sys
import platform

if sys.version_info < (3, 8):
    raise ImportError("Requires Python 3.8+")
if platform.system() != "Linux":
    raise ImportError("Linux only")

__init__.py在导入时检查Python版本和平台,如果条件不满足则引发ImportError。这确保在任何代码执行之前都兼容。

流行包中的常见模式

本节将探讨流行包中的模式,展示实际的__init__.py应用,如应用程序工厂和日志记录设置。

Flask式初始化

受Flask启发,本示例创建了一个用于延迟初始化的应用程序工厂。

apppkg/__init__.py
"""Application package initialization."""
from .core import App

_app = None

def create_app(config=None):
    """Create or return the app instance."""
    global _app
    if _app is None:
        _app = App(config or {})
    return _app

__init__.py使用create_app设置一个单例应用程序实例。它从core导入App并在需要时才初始化。

apppkg/core.py
"""Core application module."""
class App:
    def __init__(self, config):
        self.config = config
    def run(self):
        return "Running"

core.py模块定义了App类,该类存储配置并提供run方法,构成了应用程序的核心。

main.py
from apppkg import create_app

app = create_app({"debug": True})
print(app.run())  # Output: Running

脚本使用配置创建应用程序实例并调用run,展示了工厂模式的实际应用。这会将初始化推迟到显式请求时,这是一种常见的框架方法。

附加示例:日志记录设置

本示例在导入时配置日志记录,这是一种用于一致包日志记录的常见模式。

logpkg/__init__.py
"""Logging package initialization."""
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("logpkg")

def log_info(message):
    """Log an info message."""
    logger.info(message)

__init__.py将Python的日志记录配置为INFO级别,并创建一个特定于包的日志记录器。log_info函数提供了一个简单的日志记录接口。

main.py
from logpkg import log_info

log_info("Process started")  # Output: INFO:logpkg:Process started

脚本使用log_info记录一条消息,该消息会显示时间戳和日志级别。此设置可确保在包的整个使用过程中进行一致的日志记录。

测试 __init__.py

测试可确保__init__.py按预期运行。本示例验证了textutils包的关键方面。

tests/test_init.py
import textutils

def test_version():
    assert textutils.__version__ == "1.2.0"

def test_imports():
    from textutils import to_uppercase
    assert to_uppercase("test") == "TEST"

def test_all():
    assert "to_uppercase" in textutils.__all__

这些测试检查了textutils包的版本、导入功能和__all__内容。它们确保__init__.py维护了一个稳定且正确的公共API。

最佳实践

本节概述了有效使用__init__.py的指南,并通过前面的示例进行了说明。

来源

这些资源提供了关于__init__.py和Python打包的更多详细信息。

作者

我叫Jan Bodnar,我是一名充满热情的程序员,拥有丰富的编程经验。我从2007年开始撰写编程文章。迄今为止,我已撰写了1400多篇文章和8本电子书。我在教授编程方面拥有十多年的经验。

列出所有 Python 教程