Python 套件 (Packages)

套件是一種組織模組的方式,將相關的模組放在同一個目錄下。套件讓大型專案的程式碼更有結構。

什麼是套件

套件就是一個包含 __init__.py 檔案的目錄。

my_package/
    __init__.py
    module1.py
    module2.py

建立套件

目錄結構

my_project/
    main.py
    my_package/
        __init__.py
        greetings.py
        math_utils.py

模組內容

# my_package/greetings.py
def say_hello(name):
    return f"Hello, {name}!"

def say_goodbye(name):
    return f"Goodbye, {name}!"
# my_package/math_utils.py
def add(a, b):
    return a + b

def multiply(a, b):
    return a * b

__init__.py

__init__.py 可以是空檔案,或者用來設定套件的公開介面:

# my_package/__init__.py
from .greetings import say_hello, say_goodbye
from .math_utils import add, multiply

__all__ = ['say_hello', 'say_goodbye', 'add', 'multiply']

匯入套件

# 方法 1:匯入整個套件
import my_package
print(my_package.say_hello("Alice"))

# 方法 2:匯入特定模組
from my_package import greetings
print(greetings.say_hello("Alice"))

# 方法 3:匯入特定函數
from my_package.greetings import say_hello
print(say_hello("Alice"))

# 方法 4:匯入 __init__.py 中定義的名稱
from my_package import say_hello, add
print(say_hello("Alice"))
print(add(3, 5))

子套件

套件可以包含子套件:

my_package/
    __init__.py
    core/
        __init__.py
        base.py
        utils.py
    models/
        __init__.py
        user.py
        product.py
# 匯入子套件的模組
from my_package.core import utils
from my_package.models.user import User

相對匯入和絕對匯入

絕對匯入

使用完整的套件路徑:

# my_package/core/utils.py
from my_package.models.user import User

相對匯入

使用 . 表示相對位置:

# my_package/core/utils.py
from ..models.user import User  # .. 表示上一層
from .base import BaseClass     # . 表示同一層
語法意義
.同一層目錄
..上一層目錄
...上兩層目錄
相對匯入只能在套件內部使用,不能在直接執行的腳本中使用。

__all__ 變數

__all__ 定義了 from package import * 時會匯入的名稱:

# my_package/__init__.py
__all__ = ['say_hello', 'say_goodbye']

from .greetings import say_hello, say_goodbye
from .math_utils import add, multiply  # add 和 multiply 不在 __all__ 中
from my_package import *

say_hello("Alice")  # OK
# add(3, 5)         # NameError: 沒有匯入

套件的命名空間

# 查看套件的內容
import my_package
print(dir(my_package))

# 查看套件的檔案位置
print(my_package.__file__)

# 查看套件的名稱
print(my_package.__name__)

實際專案結構

簡單專案

my_project/
    main.py
    utils.py
    config.py

中型專案

my_project/
    main.py
    requirements.txt
    src/
        __init__.py
        core/
            __init__.py
            engine.py
        models/
            __init__.py
            user.py
        utils/
            __init__.py
            helpers.py
    tests/
        __init__.py
        test_core.py
        test_models.py

完整專案

my_project/
    README.md
    setup.py
    requirements.txt
    .gitignore
    src/
        my_package/
            __init__.py
            core/
            models/
            utils/
    tests/
    docs/
    examples/

建立可安裝的套件

setup.py

from setuptools import setup, find_packages

setup(
    name="my_package",
    version="0.1.0",
    packages=find_packages(where="src"),
    package_dir={"": "src"},
    install_requires=[
        "requests>=2.25.0",
    ],
    python_requires=">=3.8",
)

pyproject.toml(現代方式)

[build-system]
requires = ["setuptools>=45", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "my_package"
version = "0.1.0"
description = "My awesome package"
requires-python = ">=3.8"
dependencies = [
    "requests>=2.25.0",
]

[tool.setuptools.packages.find]
where = ["src"]

範例:建立實用套件

string_utils/
    __init__.py
    validators.py
    formatters.py
    converters.py
# string_utils/validators.py
import re

def is_email(text):
    pattern = r'^[\w\.-]+@[\w\.-]+\.\w+$'
    return bool(re.match(pattern, text))

def is_url(text):
    pattern = r'^https?://[\w\.-]+\.\w+'
    return bool(re.match(pattern, text))
# string_utils/formatters.py
def to_title_case(text):
    return text.title()

def to_snake_case(text):
    import re
    s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', text)
    return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()

def to_camel_case(text):
    components = text.split('_')
    return components[0] + ''.join(x.title() for x in components[1:])
# string_utils/__init__.py
from .validators import is_email, is_url
from .formatters import to_title_case, to_snake_case, to_camel_case

__all__ = [
    'is_email', 'is_url',
    'to_title_case', 'to_snake_case', 'to_camel_case'
]
# 使用套件
from string_utils import is_email, to_snake_case

print(is_email("test@example.com"))  # True
print(to_snake_case("HelloWorld"))   # hello_world