Python包(Package)是一种组织Python模块的方式,它使用目录结构来组织相关的模块。一个包本质上是一个包含__init__.py
文件的目录,该文件可以是空的,也可以包含包的初始化代码。
my_package/
├── __init__.py
├── module1.py
└── module2.py
模块(Module): 单个.py文件
包(Package): 包含多个模块的目录,必须有__init__.py
文件
让我们创建一个简单的数学计算包math_utils
:
math_utils/
├── __init__.py
├── basic.py
├── advanced.py
└── stats.py
__init__.py
内容:
"""数学工具包"""
from .basic import add, subtract
from .advanced import factorial
from .stats import mean
__version__ = "0.1.0"
__all__ = ['add', 'subtract', 'factorial', 'mean']
basic.py
内容:
def add(a, b):
"""返回两个数的和
Args:
a (int/float): 第一个数
b (int/float): 第二个数
Returns:
int/float: a和b的和
"""
return a + b
def subtract(a, b):
"""返回两个数的差
Args:
a (int/float): 第一个数
b (int/float): 第二个数
Returns:
int/float: a和b的差
"""
return a - b
可以通过以下方式安装开发中的包:
pip install -e /path/to/math_utils
然后就可以像使用其他包一样使用它:
from math_utils import add
result = add(5, 3)
print(result) # 输出: 8
API(Application Programming Interface)是软件组件之间交互的接口。在Python包中,API通常指包提供给外部使用的公开函数、类和方法的集合。
一致性: 相似的函数应该有相似的签名和行为
简洁性: 避免过度复杂的接口
明确性: 函数和方法的目的应该清晰明确
灵活性: 允许用户以不同方式使用API
向后兼容: 尽量避免破坏性更改
让我们实现一个简单的天气API客户端:
import requests
from typing import Dict, Optional
class WeatherClient:
"""天气API客户端
Attributes:
api_key (str): API访问密钥
base_url (str): API基础URL
"""
def __init__(self, api_key: str, base_url: str = "https://api.weatherapi.com/v1"):
"""初始化天气客户端
Args:
api_key: API访问密钥
base_url: API基础URL,默认为weatherapi的v1版本
"""
self.api_key = api_key
self.base_url = base_url
def get_current_weather(self, city: str) -> Dict:
"""获取当前天气信息
Args:
city: 城市名称
Returns:
dict: 包含天气信息的字典
Raises:
requests.exceptions.RequestException: 如果API请求失败
"""
endpoint = f"{self.base_url}/current.json"
params = {
'key': self.api_key,
'q': city,
'aqi': 'no'
}
try:
response = requests.get(endpoint, params=params)
response.raise_for_status() # 检查请求是否成功
return response.json()
except requests.exceptions.RequestException as e:
print(f"获取天气数据失败: {e}")
raise
# 初始化客户端
client = WeatherClient(api_key="your_api_key_here")
try:
# 获取北京的当前天气
weather_data = client.get_current_weather("Beijing")
# 打印温度信息
current_temp = weather_data['current']['temp_c']
print(f"当前温度: {current_temp}°C")
# 打印天气状况
condition = weather_data['current']['condition']['text']
print(f"天气状况: {condition}")
except Exception as e:
print(f"发生错误: {e}")
__all__
控制导入__all__
变量定义了当使用from package import *
时要导入的内容:
# __init__.py
__all__ = ['add', 'subtract'] # 只导出add和subtract函数
可以创建更复杂的包结构:
my_package/
├── __init__.py
├── utils/
│ ├── __init__.py
│ ├── math_utils.py
│ └── string_utils.py
└── core/
├── __init__.py
└── processor.py
有时可能需要根据条件动态导入模块:
def get_processor(processor_type):
"""根据类型动态获取处理器
Args:
processor_type (str): 处理器类型
Returns:
Processor: 相应的处理器实例
"""
if processor_type == "fast":
from .core.fast_processor import FastProcessor
return FastProcessor()
elif processor_type == "safe":
from .core.safe_processor import SafeProcessor
return SafeProcessor()
else:
raise ValueError(f"未知的处理器类型: {processor_type}")
良好的文档字符串应该包含:
功能描述
参数说明
返回值说明
可能抛出的异常
使用示例
示例:
def calculate_circle_area(radius: float) -> float:
"""计算圆的面积
根据给定的半径计算圆的面积,使用公式:面积 = π * r²
Args:
radius: 圆的半径,必须为正数
Returns:
圆的面积
Raises:
ValueError: 如果半径为负数
Examples:
>>> calculate_circle_area(3)
28.274333882308138
"""
if radius < 0:
raise ValueError("半径不能为负数")
return math.pi * (radius ** 2)
使用unittest
或pytest
为包编写测试:
import unittest
from math_utils.basic import add, subtract
class TestBasicMath(unittest.TestCase):
"""测试基础数学函数"""
def test_add(self):
"""测试加法函数"""
self.assertEqual(add(2, 3), 5)
self.assertEqual(add(-1, 1), 0)
self.assertAlmostEqual(add(0.1, 0.2), 0.3, places=7)
def test_subtract(self):
"""测试减法函数"""
self.assertEqual(subtract(5, 3), 2)
self.assertEqual(subtract(3, 5), -2)
self.assertAlmostEqual(subtract(0.3, 0.1), 0.2, places=7)
if __name__ == '__main__':
unittest.main()
setup.py
是包安装和分发的配置文件:
from setuptools import setup, find_packages
setup(
name="math_utils",
version="0.1.0",
description="一个实用的数学工具包",
long_description=open("README.md").read(),
long_description_content_type="text/markdown",
author="Your Name",
author_email="[email protected]",
url="https://github.com/yourusername/math_utils",
packages=find_packages(),
install_requires=[
'numpy>=1.18.0', # 如果有依赖项
],
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
python_requires='>=3.6',
)
# 安装构建工具
pip install setuptools wheel twine
# 构建分发文件
python setup.py sdist bdist_wheel
# 上传到PyPI
twine upload dist/*
让我们开发一个更完整的新闻API客户端:
import requests
from datetime import datetime
from typing import List, Dict, Optional
class NewsAPIClient:
"""新闻API客户端
提供了访问新闻API的功能,可以获取头条新闻、按类别搜索新闻等。
Attributes:
api_key (str): API访问密钥
base_url (str): API基础URL
timeout (int): 请求超时时间(秒)
"""
def __init__(self, api_key: str,
base_url: str = "https://newsapi.org/v2",
timeout: int = 10):
"""初始化新闻API客户端
Args:
api_key: API访问密钥
base_url: API基础URL,默认为newsapi的v2版本
timeout: 请求超时时间,默认为10秒
"""
self.api_key = api_key
self.base_url = base_url
self.timeout = timeout
self.session = requests.Session()
self.session.headers.update({'Authorization': f'Bearer {api_key}'})
def get_top_headlines(self,
country: Optional[str] = None,
category: Optional[str] = None,
sources: Optional[str] = None,
q: Optional[str] = None,
page_size: int = 20) -> List[Dict]:
"""获取头条新闻
Args:
country: 国家代码,如'us', 'gb'等
category: 新闻类别,如'business', 'entertainment'等
sources: 新闻来源ID,逗号分隔
q: 关键词搜索
page_size: 每页结果数量(1-100)
Returns:
包含新闻文章的字典列表
Raises:
requests.exceptions.RequestException: 如果API请求失败
ValueError: 如果参数无效
"""
endpoint = f"{self.base_url}/top-headlines"
params = {
'country': country,
'category': category,
'sources': sources,
'q': q,
'pageSize': page_size
}
# 清理None值的参数
params = {k: v for k, v in params.items() if v is not None}
if not any([country, category, sources]):
raise ValueError("必须提供country、category或sources中的一个")
try:
response = self.session.get(
endpoint,
params=params,
timeout=self.timeout
)
response.raise_for_status()
return response.json().get('articles', [])
except requests.exceptions.RequestException as e:
print(f"获取头条新闻失败: {e}")
raise
def search_news(self,
q: str,
search_in: Optional[str] = None,
domains: Optional[str] = None,
exclude_domains: Optional[str] = None,
from_date: Optional[datetime] = None,
to_date: Optional[datetime] = None,
language: str = 'en',
sort_by: str = 'publishedAt',
page_size: int = 20) -> List[Dict]:
"""搜索新闻
Args:
q: 搜索关键词
search_in: 搜索字段(title, content, description),逗号分隔
domains: 限制的域名,逗号分隔
exclude_domains: 排除的域名,逗号分隔
from_date: 开始日期
to_date: 结束日期
language: 语言代码,如'en', 'es'等
sort_by: 排序方式(publishedAt, relevancy, popularity)
page_size: 每页结果数量(1-100)
Returns:
包含新闻文章的字典列表
Raises:
requests.exceptions.RequestException: 如果API请求失败
"""
endpoint = f"{self.base_url}/everything"
params = {
'q': q,
'searchIn': search_in,
'domains': domains,
'excludeDomains': exclude_domains,
'from': from_date.strftime('%Y-%m-%d') if from_date else None,
'to': to_date.strftime('%Y-%m-%d') if to_date else None,
'language': language,
'sortBy': sort_by,
'pageSize': page_size
}
# 清理None值的参数
params = {k: v for k, v in params.items() if v is not None}
try:
response = self.session.get(
endpoint,
params=params,
timeout=self.timeout
)
response.raise_for_status()
return response.json().get('articles', [])
except requests.exceptions.RequestException as e:
print(f"搜索新闻失败: {e}")
raise
def close(self):
"""关闭客户端,释放资源"""
self.session.close()
使用示例
# 初始化客户端
news_client = NewsAPIClient(api_key="your_api_key_here")
try:
# 获取美国的技术类头条新闻
headlines = news_client.get_top_headlines(country="us", category="technology")
# 打印前5条新闻
for i, article in enumerate(headlines[:5], 1):
print(f"{i}. {article['title']}")
print(f"来源: {article['source']['name']}")
print(f"URL: {article['url']}\n")
# 搜索关于Python编程的新闻
python_news = news_client.search_news(q="Python programming", page_size=3)
print("\n关于Python编程的新闻:")
for article in python_news:
print(f"- {article['title']} ({article['publishedAt']})")
finally:
# 确保关闭客户端
news_client.close()
版本控制: 对API进行版本控制(如/v1/, /v2/)
错误处理: 提供清晰明确的错误信息
速率限制: 实现适当的API调用限制
认证: 使用安全的认证方式(API密钥、OAuth等)
文档: 提供完整的API文档
模块化: 将功能分解为小的、专注的模块
清晰的API边界: 明确哪些是公开API,哪些是内部实现
测试覆盖: 为关键功能编写测试
文档: 提供良好的文档字符串和用户文档
版本管理: 遵循语义化版本控制(SemVer)
本文详细介绍了Python包的开发过程,从基础概念到实际API开发,涵盖了以下内容:
Python包的基本结构和创建方法
API设计原则和实现技巧
包的文档编写和测试
包的发布流程
实际API开发案例
最佳实践和建议
通过本教程,你应该能够:
创建结构良好的Python包
设计实用的API接口
编写清晰的文档和测试
打包和发布你的Python包
开发实用的API客户端
希望这篇博客能帮助你在Python包开发和API设计方面取得进步!