Python实用工具:lunar-python库从入门到精通

一、lunar-python库核心概览

lunar-python是一款纯Python实现、无第三方依赖的历法处理库,核心用于公历、农历、佛历、道历的相互转换,同时集成干支、生肖、节气、八字、节假日、吉凶方位等传统文化要素计算。其原理基于中国传统历法算法与天文数据,通过Solar、Lunar等核心模块实现日期映射与信息解析。优点是跨平台兼容、API简洁、功能全面、支持0001-9999年超宽日期范围;缺点是部分命理类功能偏小众,无可视化模块。该库采用MIT License,可自由商用、修改与分发。

二、lunar-python库安装与基础配置

2.1 环境准备与安装

lunar-python支持Python 3.6及以上版本,安装前需确保Python环境正常,通过pip一键安装即可:

# 安装最新版lunar-python
pip install lunar-python

# 安装指定版本(可选,如1.4.7)
pip install lunar-python==1.4.7

安装完成后,可通过以下代码验证安装是否成功:

# 验证lunar-python安装
try:
    from lunar import Lunar, Solar
    print("lunar-python安装成功!")
except ImportError:
    print("lunar-python安装失败,请检查pip环境或重新安装")

2.2 核心模块导入

lunar-python的核心功能集中在lunar包下,常用模块包括:

  • Lunar:农历日期核心类,处理农历创建、转换、信息查询
  • Solar:公历日期核心类,处理公历创建、转换、信息查询
  • EightChar:八字计算模块,用于命理分析
  • Holiday:节假日查询模块,支持法定节假日与传统节日
  • JieQi:二十四节气计算模块
    基础使用只需导入LunarSolar即可:
# 导入核心模块
from lunar import Lunar, Solar

三、lunar-python基础使用:日期创建与转换

3.1 公历日期创建与基础信息查询

Solar类用于处理公历日期,可通过fromYmd方法创建公历对象,支持查询年、月、日、星期、星座、儒略日等基础信息:

# 1. 创建公历日期对象(2026年3月3日,2026年元宵节)
solar_date = Solar.fromYmd(2026, 3, 3)

# 2. 查询公历基础信息
print("公历日期:", solar_date.toFullString())  # 完整公历字符串
print("年份:", solar_date.getYear())
print("月份:", solar_date.getMonth())
print("日期:", solar_date.getDay())
print("星期:", solar_date.getWeekInChinese())  # 中文星期
print("星座:", solar_date.getXingZuo())
print("儒略日:", solar_date.getJulianDay())

# 3. 公历日期运算(加1天、减1个月)
solar_next_day = solar_date.next(1)  # 加1天
solar_prev_month = solar_date.next(-30)  # 减30天(近似1个月)
print("次日公历:", solar_next_day.toFullString())
print("前一个月公历:", solar_prev_month.toFullString())

代码说明fromYmd(year, month, day)接收公历年月日创建对象;toFullString()返回包含完整信息的字符串;next(n)可实现日期加减,n为正数加、负数减,单位为天。

3.2 农历日期创建与基础信息查询

Lunar类用于处理农历日期,支持通过fromYmd创建(需注意闰月参数),可查询农历年、月、日、干支、生肖、节气等信息:

# 1. 创建农历日期对象(2026年正月十五,元宵节,无闰月)
lunar_date = Lunar.fromYmd(2026, 1, 15)

# 2. 创建闰月农历对象(示例:2025年闰六月初十,第四个参数为True表示闰月)
lunar_leap = Lunar.fromYmd(2025, 6, 10, True)

# 3. 查询农历基础信息
print("农历日期:", lunar_date.toFullString())  # 完整农历字符串
print("农历年:", lunar_date.getYearInChinese())  # 中文农历年
print("农历月:", lunar_date.getMonthInChinese())  # 中文农历月
print("农历日:", lunar_date.getDayInChinese())  # 中文农历日
print("干支纪年:", lunar_date.getYearGanZhi())
print("生肖:", lunar_date.getShengXiao())
print("是否闰月:", lunar_date.isLeapMonth())
print("当前节气:", lunar_date.getJieQi())

# 4. 农历日期运算
lunar_next_day = lunar_date.next(1)  # 加1天
print("次日农历:", lunar_next_day.toFullString())

代码说明Lunar.fromYmd(year, month, day, isLeapMonth=False)中,isLeapMonth用于标记是否为闰月,默认False;getYearGanZhi()返回干支纪年,getShengXiao()自动匹配生肖。

3.3 公历与农历相互转换

lunar-python最核心的功能是公历↔农历双向转换,通过getLunar()(公历转农历)和getSolar()(农历转公历)实现:

# 场景1:公历转农历(2026年3月3日转农历)
solar = Solar.fromYmd(2026, 3, 3)
lunar_from_solar = solar.getLunar()
print("公历2026-03-03 转农历:", lunar_from_solar.toFullString())

# 场景2:农历转公历(2026年正月十五转公历)
lunar = Lunar.fromYmd(2026, 1, 15)
solar_from_lunar = lunar.getSolar()
print("农历2026年正月十五 转公历:", solar_from_lunar.toFullString())

# 场景3:批量转换(10天内公历转农历)
print("\n未来10天公历转农历结果:")
for i in range(10):
    solar_batch = Solar.fromYmd(2026, 3, 3).next(i)
    lunar_batch = solar_batch.getLunar()
    print(f"公历:{solar_batch.toYmd()} → 农历:{lunar_batch.toFullString()}")

代码说明:双向转换无需额外参数,直接调用对象方法即可;批量转换可结合next()方法实现,适用于日历生成、节日统计等场景。

四、lunar-python进阶功能:传统文化要素查询

4.1 二十四节气与物候查询

JieQi模块与Lunar类集成,可精准查询任意日期的节气、物候,支持节气时间计算:

from lunar import JieQi

# 1. 查询指定日期的节气(2026年3月3日)
lunar = Lunar.fromYmd(2026, 1, 15)
jieqi = lunar.getJieQi()
print(f"2026年正月十五对应节气:{jieqi}")

# 2. 查询2026年所有节气及公历日期
print("\n2026年二十四节气:")
for i in range(24):
    jieqi_name = JieQi.getName(i)
    jieqi_solar = JieQi.getSolar(2026, i)
    print(f"{jieqi_name}:{jieqi_solar.toYmd()}")

# 3. 查询节气物候(以立春为例)
li_chun = JieQi.getSolar(2026, 0)  # 0对应立春
wu_hou = li_chun.getLunar().getWuHou()
print(f"\n立春物候:{wu_hou}")

代码说明JieQi.getName(index)通过索引(0-23)获取节气名称;JieQi.getSolar(year, index)获取指定年份、索引的节气公历日期;getWuHou()返回对应节气的物候信息。

4.2 干支、纳音与五行查询

干支纪年、纳音五行是传统文化核心要素,lunar-python可精准计算年、月、日、时的干支与五行:

# 1. 查询年月日时干支(2026年正月十五 子时)
lunar = Lunar.fromYmdHms(2026, 1, 15, 0, 0, 0)  # 含时辰的农历对象
print("年干支:", lunar.getYearGanZhi())
print("月干支:", lunar.getMonthGanZhi())
print("日干支:", lunar.getDayGanZhi())
print("时干支:", lunar.getTimeGanZhi())

# 2. 查询纳音五行
na_yin = lunar.getNaYin()
print("纳音五行:", na_yin)

# 3. 查询五行属性
wu_xing = lunar.getWuXing()
print("五行属性:", wu_xing)

代码说明fromYmdHms()可创建含时辰的农历对象,时辰范围0-23;getNaYin()返回年月日时的纳音组合,getWuXing()返回五行属性。

4.3 节假日与传统节日查询

Holiday模块支持查询法定节假日、调休及传统节日,可判断日期是否为节日:

from lunar import Holiday

# 1. 判断指定日期是否为节假日(2026年3月3日,元宵节)
solar = Solar.fromYmd(2026, 3, 3)
holiday_name = Holiday.getName(solar)
is_holiday = Holiday.isHoliday(solar)
is_work = Holiday.isWork(solar)
print(f"2026-03-03 节日:{holiday_name}")
print(f"是否节假日:{is_holiday}")
print(f"是否工作日:{is_work}")

# 2. 查询2026年所有法定节假日
print("\n2026年法定节假日:")
holidays = Holiday.getHolidays(2026)
for hd in holidays:
    print(f"{hd['name']}:{hd['start']} 至 {hd['end']}")

# 3. 批量判断未来7天是否为节日
print("\n未来7天节日查询:")
for i in range(7):
    s = solar.next(i)
    name = Holiday.getName(s)
    print(f"{s.toYmd()}:{name if name else '无节日'}")

代码说明Holiday.getName(solar)返回节日名称,无则为空;isHoliday()判断是否为节假日,isWork()判断是否为调休工作日;getHolidays(year)返回指定年份的节假日列表。

4.4 吉凶方位与彭祖百忌查询

lunar-python集成老黄历功能,可查询每日吉神、凶煞方位及彭祖百忌:

lunar = Lunar.fromYmd(2026, 1, 15)

# 1. 查询吉神方位(喜神、福神、财神、贵神)
xi_shen = lunar.getXiShenFangWei()
fu_shen = lunar.getFuShenFangWei()
cai_shen = lunar.getCaiShenFangWei()
yang_gui = lunar.getYangGuiShenFangWei()
yin_gui = lunar.getYinGuiShenFangWei()
print(f"喜神方位:{xi_shen}")
print(f"福神方位:{fu_shen}")
print(f"财神方位:{cai_shen}")
print(f"阳贵神方位:{yang_gui}")
print(f"阴贵神方位:{yin_gui}")

# 2. 查询彭祖百忌
peng_zu = lunar.getPengZuBaiJi()
print(f"彭祖百忌:{peng_zu}")

# 3. 查询冲煞信息
chong_sha = lunar.getChongSha()
print(f"冲煞:{chong_sha}")

代码说明:方位返回中文方位词(如东南、正南);彭祖百忌返回当日禁忌;冲煞返回冲犯生肖与方位。

4.5 八字命理计算

EightChar模块支持八字排盘,可查询四柱八字、大运、流年等命理信息:

from lunar import EightChar

# 1. 创建八字对象(2026年正月十五 子时)
lunar = Lunar.fromYmdHms(2026, 1, 15, 0, 0, 0)
eight_char = EightChar(lunar)

# 2. 查询四柱八字
print("四柱八字:", eight_char.getFourPillar())
print("年柱:", eight_char.getYearPillar())
print("月柱:", eight_char.getMonthPillar())
print("日柱:", eight_char.getDayPillar())
print("时柱:", eight_char.getTimePillar())

# 3. 查询大运(起运岁数、大运干支)
da_yun = eight_char.getDaYun()
print("\n大运:")
for dy in da_yun:
    print(f"{dy['age']}岁:{dy['ganZhi']}")

# 4. 查询流年(2026-2030年流年)
print("\n2026-2030年流年:")
for year in range(2026, 2031):
    liu_nian = eight_char.getLiuNian(year)
    print(f"{year}年:{liu_nian}")

代码说明:八字计算需基于含时辰的农历对象;getFourPillar()返回完整四柱,getDaYun()返回大运信息,getLiuNian(year)返回指定年份的流年干支。

五、lunar-python实战案例:多功能日历生成工具

5.1 案例需求

开发一个Python脚本,实现以下功能:

  1. 输入公历日期,输出对应农历、干支、生肖、节气、节日
  2. 生成指定月份的公历-农历对照日历
  3. 批量查询未来30天的吉神方位与彭祖百忌
  4. 支持命令行交互,操作简单

5.2 完整代码实现

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from lunar import Lunar, Solar, Holiday

def query_single_date(year, month, day):
    """查询单个公历日期的详细信息"""
    try:
        solar = Solar.fromYmd(year, month, day)
        lunar = solar.getLunar()
        # 基础信息
        print("="*50)
        print(f"【公历日期】{solar.toFullString()}")
        print(f"【对应农历】{lunar.toFullString()}")
        print(f"【干支纪年】{lunar.getYearGanZhi()} 【生肖】{lunar.getShengXiao()}")
        print(f"【当前节气】{lunar.getJieQi() if lunar.getJieQi() else '无'}")
        # 节日信息
        holiday = Holiday.getName(solar)
        print(f"【节日】{holiday if holiday else '无'}")
        # 黄历信息
        print(f"【喜神方位】{lunar.getXiShenFangWei()} 【财神方位】{lunar.getCaiShenFangWei()}")
        print(f"【彭祖百忌】{lunar.getPengZuBaiJi()}")
        print("="*50 + "\n")
    except Exception as e:
        print(f"日期查询错误:{e}")

def generate_month_calendar(year, month):
    """生成指定月份的公历-农历对照日历"""
    print(f"\n{'='*20} {year}年{month}月 公历-农历对照日历 {'='*20}")
    print(f"{'公历日期':<12} {'农历日期':<12} {'星期':<4} {'节日':<8} {'节气':<6}")
    print("-"*60)
    # 获取当月天数
    first_day = Solar.fromYmd(year, month, 1)
    for i in range(31):
        try:
            solar = first_day.next(i)
            if solar.getMonth() != month:
                break
            lunar = solar.getLunar()
            holiday = Holiday.getName(solar) or "无"
            jieqi = lunar.getJieQi() or "无"
            print(f"{solar.toYmd():<12} {lunar.toYmd():<12} {solar.getWeekInChinese():<4} {holiday:<8} {jieqi:<6}")
        except:
            break
    print("-"*60 + "\n")

def batch_query_thirty_days(year, month, day):
    """批量查询未来30天的黄历信息"""
    print(f"\n{'='*20} 未来30天黄历查询 {'='*20}")
    start_solar = Solar.fromYmd(year, month, day)
    for i in range(30):
        solar = start_solar.next(i)
        lunar = solar.getLunar()
        print(f"{solar.toYmd()} | 农历:{lunar.toYmd()} | 喜神:{lunar.getXiShenFangWei()} | 彭祖百忌:{lunar.getPengZuBaiJi()}")
    print("="*60 + "\n")

def main():
    """主函数:命令行交互"""
    print("欢迎使用lunar-python多功能日历工具")
    while True:
        print("\n请选择功能:")
        print("1. 查询单个日期详细信息")
        print("2. 生成月份公历-农历对照日历")
        print("3. 批量查询未来30天黄历")
        print("4. 退出工具")
        choice = input("请输入功能编号(1-4):")
        if choice == "1":
            try:
                y = int(input("请输入公历年份:"))
                m = int(input("请输入公历月份:"))
                d = int(input("请输入公历日期:"))
                query_single_date(y, m, d)
            except ValueError:
                print("输入错误,请输入有效数字!")
        elif choice == "2":
            try:
                y = int(input("请输入年份:"))
                m = int(input("请输入月份:"))
                generate_month_calendar(y, m)
            except ValueError:
                print("输入错误,请输入有效数字!")
        elif choice == "3":
            try:
                y = int(input("请输入起始公历年份:"))
                m = int(input("请输入起始公历月份:"))
                d = int(input("请输入起始公历日期:"))
                batch_query_thirty_days(y, m, d)
            except ValueError:
                print("输入错误,请输入有效数字!")
        elif choice == "4":
            print("感谢使用,再见!")
            break
        else:
            print("输入无效,请重新选择!")

if __name__ == "__main__":
    main()

5.3 代码说明与运行效果

  1. 函数设计:拆分query_single_date(单日期查询)、generate_month_calendar(月历生成)、batch_query_thirty_days(批量查询)、main(交互入口)4个函数,结构清晰,便于维护。
  2. 异常处理:加入try-except捕获日期格式错误、输入非数字等异常,提升工具稳定性。
  3. 功能覆盖:整合基础转换、节日查询、黄历信息、批量处理等核心功能,满足日常日历需求。
  4. 运行方式:保存为lunar_calendar.py,命令行执行python lunar_calendar.py,按提示操作即可。

六、lunar-python库相关资源

6.1 官方资源地址

  • PyPI地址:https://pypi.org/project/lunar-python/
  • GitHub地址:https://github.com/6tail/lunar-python
  • 官方文档地址:http://6tail.cn/calendar/api.html

6.2 资源使用建议

  1. PyPI页面可查看版本更新日志、依赖信息,安装时可指定稳定版本。
  2. GitHub仓库包含完整源码、示例代码、issue记录,可提交bug或功能建议。
  3. 官方文档提供详细API说明、参数解释、进阶示例,是开发的核心参考。

关注我,每天分享一个实用的Python自动化工具。

Python屏幕截图神器:python‑mss 极速截图与屏幕录制实战教程

一、python‑mss 库简介

python‑mss 是一款专注于极速屏幕截图的 Python 第三方库,核心用于跨平台屏幕捕获、区域截图、多显示器截图。其基于操作系统原生 API 实现截图逻辑,不依赖 PIL、OpenCV 等外部图像处理库,截图速度远超传统库。该库采用 MIT 开源许可证,可自由商用、修改与分发,优点是轻量、极速、跨平台、低内存占用,缺点是仅专注截图,不自带图像编辑功能。

二、python‑mss 安装方法

python‑mss 对 Python 环境兼容性极强,支持 Python 3.6 及以上版本,可在 Windows、macOS、Linux 三大操作系统正常运行,安装仅需一条 pip 命令,无需额外配置依赖环境。

打开电脑终端(Windows 用 CMD 或 PowerShell,macOS/Linux 用终端),执行以下安装命令:

pip install python-mss

若遇到网络卡顿导致安装失败,可切换国内 PyPI 镜像源加速安装:

pip install python-mss -i https://pypi.tuna.tsinghua.edu.cn/simple

安装完成后,可通过以下命令验证是否安装成功:

pip show python-mss

执行后若能看到库的版本、安装路径等信息,即代表安装无误,可直接进入代码使用环节。

三、python‑mss 核心功能与基础代码示例

3.1 快速获取全屏截图

python‑mss 最基础的功能就是全屏截图,无需复杂配置,导入库后创建截图对象,调用截图方法即可完成操作,代码简洁易懂,适合零基础开发者快速上手。

# 导入python-mss核心模块
import mss
import mss.tools

# 创建截图对象
with mss.mss() as sct:
    # 获取默认显示器的全屏截图
    screenshot = sct.grab(sct.monitors[0])

    # 将截图保存为PNG图片
    mss.tools.to_png(screenshot.rgb, screenshot.size, output="full_screen.png")

print("全屏截图已保存为 full_screen.png")

代码说明

  1. import mssimport mss.tools 分别导入截图核心模块与图片保存工具模块;
  2. with mss.mss() as sct 使用上下文管理器创建截图对象,无需手动释放资源,更安全;
  3. sct.monitors[0] 代表获取所有显示器的全屏区域,单显示器环境下就是整个屏幕;
  4. sct.grab() 方法执行截图操作,返回包含图像数据的对象;
  5. mss.tools.to_png() 将截图的 RGB 数据转换为 PNG 格式并保存到本地。

运行代码后,会在脚本同级目录生成 full_screen.png 文件,即为当前屏幕的完整截图。

3.2 自定义区域截图(指定坐标截图)

实际使用中,我们往往不需要全屏截图,只需要截取屏幕上某一个指定区域,python‑mss 支持通过左上角坐标、宽度、高度精准控制截图范围,满足窗口截图、局部内容截取等场景。

import mss
import mss.tools

# 定义截图区域:left=左上角x坐标, top=左上角y坐标, width=宽度, height=高度
capture_area = {
    "left": 100,
    "top": 100,
    "width": 800,
    "height": 600
}

with mss.mss() as sct:
    # 截取指定区域
    area_img = sct.grab(capture_area)
    # 保存截图
    mss.tools.to_png(area_img.rgb, area_img.size, output="custom_area.png")

print(f"已截取区域:{capture_area},保存为 custom_area.png")

代码说明

  1. capture_area 是一个字典,用于定义截图的位置和大小,四个参数缺一不可;
  2. lefttop 控制截图区域在屏幕上的起始位置,数值越大位置越靠右、靠下;
  3. widthheight 控制截图的宽度和高度,决定截图区域的大小;
  4. 其余逻辑与全屏截图一致,仅将全屏区域替换为自定义区域。

开发者可根据自身需求修改字典内的数值,实现精准区域截取,比如截取浏览器某一块内容、软件某一个窗口等。

3.3 多显示器截图

对于使用双屏、三屏的办公或开发用户,python‑mss 提供了完善的多显示器支持,可以单独截取某一个显示器的画面,也可以截取所有显示器拼接画面,适配多屏工作场景。

import mss
import mss.tools

with mss.mss() as sct:
    # 查看当前设备所有显示器信息
    print("显示器信息:", sct.monitors)

    # 截取第一个显示器(主显示器)画面
    monitor_1 = sct.grab(sct.monitors[1])
    mss.tools.to_png(monitor_1.rgb, monitor_1.size, output="monitor_1.png")

    # 若有第二个显示器,可直接截取
    if len(sct.monitors) > 2:
        monitor_2 = sct.grab(sct.monitors[2])
        mss.tools.to_png(monitor_2.rgb, monitor_2.size, output="monitor_2.png")

代码说明

  1. sct.monitors 会返回一个列表,包含所有显示器的区域信息,列表第一个元素是所有显示器总区域;
  2. sct.monitors[1] 代表主显示器,sct.monitors[2] 代表副显示器,以此类推;
  3. 代码中增加了判断逻辑,避免只有单显示器时运行报错,兼容性更强;
  4. 多屏截图的方法与单屏完全一致,仅需修改 monitors 后的索引值。

运行代码后,终端会打印出当前设备的显示器数量与区域参数,同时自动保存对应显示器的截图文件。

3.4 实时获取截图数据(不保存本地)

python‑mss 不仅可以将截图保存到本地,还能直接获取截图的原始像素数据,配合 PIL、OpenCV 等库,可实现实时图像处理、屏幕识别、自动化操作等进阶功能,无需先保存再读取,效率更高。

import mss
import numpy as np

with mss.mss() as sct:
    # 截取主显示器
    img = sct.grab(sct.monitors[1])

    # 将截图转换为numpy数组(方便后续图像处理)
    img_np = np.array(img)

    # 输出图片基本信息
    print("截图形状(高, 宽, 通道):", img_np.shape)
    print("截图像素数据类型:", img_np.dtype)

代码说明

  1. 该示例引入 numpy 库,将截图对象转换为数组,这是计算机视觉、图像识别的常用操作;
  2. sct.grab() 返回的对象可直接转换为数组,无需额外转换步骤;
  3. 转换后的数组包含屏幕画面的所有像素信息,可直接用于后续分析、标注、识别等操作;
  4. 此方式不生成本地图片,适合实时处理场景,比如游戏辅助、屏幕监控、自动化脚本等。

运行代码后,终端会打印出截图的尺寸、通道数等数据,证明已成功获取原始图像信息。

3.5 极速连续截图(模拟录屏功能)

凭借轻量化、高性能的优势,python‑mss 可实现高频率连续截图,配合循环语句,就能实现轻量级录屏效果,适合制作简单的屏幕录制脚本,无需安装专业录屏软件。

import mss
import mss.tools
import time

# 设置截图次数与间隔
capture_count = 10
interval = 0.5

with mss.mss() as sct() as sct:
    monitor = sct.monitors[1]
    for i in range(capture_count):
        # 执行截图
        frame = sct.grab(monitor)
        # 按序号保存帧图片
        filename = f"frame_{i+1:02d}.png"
        mss.tools.to_png(frame.rgb, frame.size, output=filename)
        print(f"已保存第 {i+1} 帧:{filename}")
        # 等待指定时间
        time.sleep(interval)

print(f"连续截图完成,共生成 {capture_count} 帧画面")

代码说明

  1. capture_count 控制截图总次数,interval 控制每帧之间的间隔时间,单位为秒;
  2. 通过 for 循环实现连续截图,文件名按序号排列,方便后续合成视频;
  3. time.sleep() 用于控制截图频率,数值越小截图速度越快,越接近录屏效果;
  4. 该代码可直接用于制作简易录屏脚本,后续可配合 cv2.VideoWriter 将帧合成视频。

运行代码后,脚本会每隔 0.5 秒截取一次屏幕,共生成 10 张连续截图,可直观看到 python‑mss 的高速截图能力。

四、python‑mss 结合实际场景综合案例

4.1 场景需求

开发一款简易屏幕区域实时监控工具,实现功能:

  1. 自定义截取屏幕指定区域;
  2. 实时显示截图画面;
  3. 将实时画面保存为连续帧;
  4. 代码轻量化,不依赖重型库。

4.2 完整实现代码

import mss
import mss.tools
import numpy as np
import cv2
import time

def screen_monitor(left, top, width, height, save_frames=True, max_frames=30):
    """
    屏幕区域实时监控函数
    :param left: 区域左上角x坐标
    :param top: 区域左上角y坐标
    :param width: 截图宽度
    :param height: 截图高度
    :param save_frames: 是否保存帧图片
    :param max_frames: 最大截图帧数
    """
    # 定义截图区域
    monitor_area = {"left": left, "top": top, "width": width, "height": height}

    with mss.mss() as sct:
        frame_count = 0
        start_time = time.time()

        while frame_count < max_frames:
            # 极速截图
            frame = sct.grab(monitor_area)
            # 转换为OpenCV可识别的格式
            frame_bgr = np.array(frame)
            # 显示实时画面
            cv2.imshow("Screen Monitor - 按ESC退出", frame_bgr)

            # 保存帧图片
            if save_frames:
                mss.tools.to_png(frame.rgb, frame.size, output=f"monitor_frame_{frame_count+1:02d}.png")

            frame_count += 1
            # 按ESC键退出
            if cv2.waitKey(1) & 0xFF == 27:
                break

        # 计算性能
        cost_time = time.time() - start_time
        fps = frame_count / cost_time
        print(f"监控完成,总帧数:{frame_count},耗时:{cost_time:.2f}s,平均FPS:{fps:.1f}")

        # 释放窗口
        cv2.destroyAllWindows()

# 启动屏幕监控:截取(200,200)位置,宽1000高600的区域
if __name__ == "__main__":
    screen_monitor(left=200, top=200, width=1000, height=600, save_frames=True, max_frames=50)

代码说明

  1. 封装了 screen_monitor 函数,支持自定义坐标、尺寸、是否保存、最大帧数,复用性极强;
  2. 结合 OpenCV 实现实时画面显示,窗口可直观看到截取区域的动态变化;
  3. 加入 FPS 计算,可直观体现 python‑mss 的截图性能,普通电脑可轻松达到 30+ FPS;
  4. 支持按 ESC 键提前退出,操作更人性化;
  5. 全程仅依赖 python‑mss、numpy、cv2 三个库,轻量化且运行流畅。

运行代码后,会弹出实时监控窗口,同时自动保存截图帧,终端打印性能数据,完整实现轻量化屏幕监控功能。

相关资源

  • Pypi地址:https://pypi.org/project/python-mss/
  • Github地址:https://github.com/BoboTiG/python-mss
  • 官方文档地址:https://python-mss.readthedocs.io/

关注我,每天分享一个实用的Python自动化工具。

Python 实用工具:catalogue 库详解——轻量级对象注册与管理神器

一、catalogue 库概述

catalogue 是一款专为 Python 设计的轻量级对象注册与管理库,核心作用是实现函数、类、实例等对象的集中注册、分类存储与快速调用,底层基于字典与装饰器实现无侵入式绑定,无需复杂配置即可完成对象映射。其优点是体积小巧、无第三方依赖、使用简洁、适配任意 Python 项目,缺点是仅专注于对象注册,不具备数据校验、动态加载等扩展功能。该库采用 MIT License,开源免费可商用。

二、catalogue 库安装方法

catalogue 作为纯 Python 实现的轻量库,不依赖任何额外包,通过 pip 即可快速完成安装,适配 Python 3.6 及以上所有版本,安装命令如下:

pip install catalogue

安装完成后,可在 Python 交互环境中导入验证,无报错即代表安装成功:

import catalogue
# 无报错则安装正常

三、catalogue 库核心使用方式

3.1 基础注册与调用

catalogue 最核心的功能是通过装饰器完成对象注册,无需修改原函数或类的逻辑,即可将其加入指定命名空间,后续通过统一接口调用。

首先创建注册器,指定命名空间名称,再使用 @registerer.register 装饰器绑定对象:

# 1. 创建注册器,命名空间为 "tools"
tool_registry = catalogue.create("tools")

# 2. 注册普通函数
@tool_registry.register("print_hello")
def hello():
    """简单的问候函数"""
    print("Hello, catalogue!")

# 3. 注册带参数的函数
@tool_registry.register("add_num")
def add(a, b):
    """数值加法函数"""
    return a + b

# 4. 通过注册名称调用对象
# 调用 hello 函数
tool_registry.get("print_hello")()
# 调用 add 函数并输出结果
result = tool_registry.get("add_num")(3, 5)
print("3 + 5 =", result)

代码说明

  • catalogue.create() 用于创建指定命名空间的注册器,不同命名空间相互隔离,避免命名冲突;
  • @registerer.register(name) 装饰器将被装饰对象绑定到注册器,name 为唯一标识;
  • registerer.get(name) 可根据标识获取注册对象,直接调用即可执行原函数逻辑。

运行结果:

Hello, catalogue!
3 + 5 = 8

3.2 类与实例注册

catalogue 不仅支持函数注册,还能注册类、类方法、实例对象,适配面向对象编程场景。

# 创建新的注册器,命名空间为 "classes"
class_registry = catalogue.create("classes")

# 注册类
@class_registry.register("Calculator")
class Calculator:
    def __init__(self, name):
        self.name = name

    def multiply(self, x, y):
        return x * y

    def __str__(self):
        return f"计算器: {self.name}"

# 获取注册的类并创建实例
CalcClass = class_registry.get("Calculator")
calc_instance = CalcClass("数学计算器")
print(calc_instance)
# 调用实例方法
print("4 * 6 =", calc_instance.multiply(4, 6))

# 直接注册实例对象
@class_registry.register("default_calc")
default_calc = CalcClass("默认计算器")

# 调用注册的实例
instance = class_registry.get("default_calc")
print("5 * 7 =", instance.multiply(5, 7))

代码说明

  • 类注册后,可通过 get() 获取类本身,再实例化使用;
  • 实例可直接注册,无需重复创建,适合单例对象管理;
  • 注册逻辑完全不侵入类的定义,保持原有代码结构不变。

运行结果:

计算器: 数学计算器
4 * 6 = 24
5 * 7 = 35

3.3 多命名空间与层级注册

catalogue 支持创建多个独立命名空间,还可通过层级路径实现细分管理,适合大型项目中模块拆分。

# 创建多层级注册器:主命名空间 "app",子命名空间 "auth"
auth_registry = catalogue.create("app", "auth")
# 创建另一个子命名空间 "api"
api_registry = catalogue.create("app", "api")

# 注册认证相关函数
@auth_registry.register("login")
def login(username, password):
    return f"用户 {username} 登录成功"

@auth_registry.register("logout")
def logout():
    return "用户退出登录"

# 注册 API 相关函数
@api_registry.register("get_user")
def get_user(uid):
    return {"id": uid, "name": "测试用户"}

# 跨命名空间调用
print(auth_registry.get("login")("admin", "123456"))
print(api_registry.get("get_user")(1001))

# 查看所有注册项
print("auth 注册项:", list(auth_registry.keys()))
print("api 注册项:", list(api_registry.keys()))

代码说明

  • catalogue.create(*names) 可传入多个字符串,创建层级化命名空间;
  • 不同层级注册器相互独立,即使注册名称相同也不会冲突;
  • registerer.keys() 可获取当前注册器内所有对象的标识,方便遍历管理。

运行结果:

用户 admin 登录成功
{'id': 1001, 'name': '测试用户'}
auth 注册项: ['login', 'logout']
api 注册项: ['get_user']

3.4 注册元数据与自定义属性

catalogue 支持在注册时添加自定义元数据,方便记录对象描述、作者、版本等附加信息,丰富注册对象的管理能力。

# 创建注册器
meta_registry = catalogue.create("meta_tools")

# 注册时传入元数据
@meta_registry.register("data_filter", author="test", version="1.0", desc="数据过滤工具")
def filter_data(data_list, condition):
    return [item for item in data_list if condition(item)]

# 获取注册对象及元数据
func = meta_registry.get("data_filter")
meta = meta_registry.get_meta("data_filter")

print("函数对象:", func)
print("元数据:", meta)

# 使用注册函数
data = [1, 2, 3, 4, 5, 6]
filtered = func(data, lambda x: x % 2 == 0)
print("过滤偶数:", filtered)

代码说明

  • register() 可接收任意关键字参数作为元数据,自动存储;
  • registerer.get_meta(name) 专门用于获取对象的元数据,不影响原对象调用;
  • 元数据适合用于插件描述、配置记录、文档自动生成等场景。

运行结果:

函数对象: <function filter_data at 0x000001234567890>
元数据: {'author': 'test', 'version': '1.0', 'desc': '数据过滤工具'}
过滤偶数: [2, 4, 6]

3.5 遍历与检查注册对象

在实际项目中,常需要遍历所有注册对象或检查对象是否存在,catalogue 提供了简洁的接口实现该功能。

# 创建注册器并注册多个对象
check_reg = catalogue.create("check_demo")

@check_reg.register("func1")
def func1(): pass

@check_reg.register("func2")
def func2(): pass

@check_reg.register("func3")
def func3(): pass

# 检查对象是否存在
print("func1 是否存在:", "func1" in check_reg)
print("func4 是否存在:", "func4" in check_reg)

# 遍历所有注册对象
for name, obj in check_reg.items():
    print(f"注册名称: {name}, 对象: {obj}")

# 获取所有注册名称
print("所有注册名称:", list(check_reg))

# 清空注册器(可选)
check_reg.clear()
print("清空后注册数量:", len(check_reg))

代码说明

  • 注册器支持 in 关键字判断对象是否存在,语法符合 Python 原生习惯;
  • items() 可遍历所有名称与对象,list(check_reg) 直接获取所有名称;
  • clear() 用于清空注册器,适合动态重置场景。

运行结果:

func1 是否存在: True
func4 是否存在: False
注册名称: func1, 对象: <function func1 at 0x000001234567890>
注册名称: func2, 对象: <function func2 at 0x000001234567891>
注册名称: func3, 对象: <function func3 at 0x000001234567892>
所有注册名称: ['func1', 'func2', 'func3']
清空后注册数量: 0

四、实际项目应用案例

4.1 插件化工具平台

在中小型项目中,常需要实现插件化功能,无需修改主程序即可新增功能,catalogue 是实现该需求的最简方案。

# 主程序:插件注册中心
plugin_registry = catalogue.create("my_app", "plugins")

# 插件1:文本处理
@plugin_registry.register("text_upper")
def to_upper(text):
    return text.upper()

# 插件2:数字统计
@plugin_registry.register("num_count")
def count_numbers(num_list):
    return{"sum": sum(num_list), "max": max(num_list), "min": min(num_list)}

# 插件3:时间格式化
@plugin_registry.register("time_format")
def format_time(timestamp):
    from datetime import datetime
    return datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S")

# 主程序调用逻辑
def run_plugin(plugin_name, *args, **kwargs):
    """统一调用插件接口"""
    if plugin_name not in plugin_registry:
        return f"插件 {plugin_name} 不存在"
    plugin_func = plugin_registry.get(plugin_name)
    return plugin_func(*args, **kwargs)

# 业务使用
print(run_plugin("text_upper", "hello python"))
print(run_plugin("num_count", [10, 20, 30, 40]))
print(run_plugin("time_format", 1735689600))

代码说明

  • 主程序只定义注册中心与调用接口,新增插件只需添加注册函数,无需改动主逻辑;
  • 统一调用入口降低业务代码耦合,适合小型工具、脚本项目快速扩展;
  • catalogue 无依赖特性,可直接嵌入脚本,无需复杂环境配置。

运行结果:

HELLO PYTHON
{'sum': 100, 'max': 40, 'min': 10}
2024-01-01 00:00:00

4.2 命令行工具命令注册

结合 catalogue 可快速实现轻量级命令行工具,替代复杂框架,适合个人脚本与自动化工具。

# 命令注册器
cmd_registry = catalogue.create("cli", "commands")

# 注册命令
@cmd_registry.register("start", desc="启动服务")
def start_service():
    print("服务启动成功")

@cmd_registry.register("stop", desc="停止服务")
def stop_service():
    print("服务已停止")

@cmd_registry.register("status", desc="查看服务状态")
def check_status():
    print("服务运行正常")

# 模拟命令行执行
import sys
def cli_main():
    if len(sys.argv) < 2:
        print("可用命令:")
        for cmd, meta in cmd_registry.items_meta():
            print(f"  {cmd}: {meta.get('desc', '无描述')}")
        return

    cmd = sys.argv[1]
    run_plugin(cmd)

# 直接测试
if __name__ == "__main__":
    # 模拟执行命令 python script.py start
    run_plugin("start")
    run_plugin("status")

代码说明

  • 命令行工具通过注册器管理所有命令,新增命令只需添加装饰器;
  • 可自动生成帮助信息,减少重复代码;
  • 适合快速开发运维脚本、自动化工具。

五、catalogue 与同类库对比

相比 pluggy、stevedore 等专业插件库,catalogue 优势在于极致轻量、零依赖、使用简单,无需学习复杂概念,适合小型项目、脚本、快速原型开发;而专业插件库适合大型项目的动态加载、多文件插件管理。

在日常脚本、小型工具、教学项目中,catalogue 足以满足对象注册、插件管理、命令绑定等需求,且不会增加项目体积,是 Python 轻量化开发的优质选择。

相关资源

  • Pypi地址:https://pypi.org/project/catalogue/
  • Github地址:https://github.com/explosion/catalogue
  • 官方文档地址:https://catalogue.readthedocs.io/

关注我,每天分享一个实用的Python自动化工具。

Python实用工具:Send2Trash 安全删除文件不翻车

一、Send2Trash 库简介

在日常使用Python处理文件时,很多开发者都会用到os.remove()或者pathlib.unlink()来删除文件,这类方法的特点是直接从磁盘上删除文件,不会经过系统回收站,一旦误删重要文件,基本无法恢复,对于生产环境、办公脚本、自动化处理程序来说,存在极大的数据丢失风险。

Send2Trash就是为了解决这一问题而生的轻量级Python第三方库,它的核心作用是将文件或文件夹移动到系统对应的回收站,而不是直接永久删除,让Python脚本的文件删除操作变得和手动右键删除一样安全可撤回。

该库采用跨平台设计,底层会自动适配Windows、macOS、Linux三大主流操作系统,调用对应平台原生的回收站接口,无需用户手动区分系统,做到一套代码全平台通用。其License为BSD许可证,属于宽松的开源协议,允许个人与商业项目免费使用、修改与分发。

Send2Trash的优点十分突出:轻量无依赖、跨平台兼容性强、使用简单、避免文件误删;缺点则是仅提供移动到回收站的基础功能,不支持文件恢复、批量任务管理等扩展能力,专注于单点核心功能。

二、Send2Trash 安装方法

Send2Trash是纯Python实现的库,不依赖其他第三方包,安装过程非常简洁,只需要使用Python自带的pip包管理器即可完成安装。

打开命令提示符(Windows)、终端(macOS/Linux),直接执行以下安装命令:

pip install Send2Trash

如果你的电脑同时安装了Python2和Python3,为了避免版本冲突,建议使用pip3进行安装:

pip3 install Send2Trash

安装完成后,不会有任何复杂的配置流程,直接在Python脚本中导入即可使用,对新手极其友好,不需要处理环境变量、编译依赖、权限配置等繁琐操作。

三、Send2Trash 基础使用方法

3.1 基础导入与删除单个文件

Send2Trash的使用逻辑非常简单,库中最核心的函数就是send2trash(),调用该函数并传入文件路径,就能将目标文件移动到回收站,而非直接删除。

下面是删除单个文件的基础示例:

# 导入Send2Trash核心函数
from send2trash import send2trash

# 定义要删除的文件路径
file_path = "test.txt"

# 调用函数,将文件移动到回收站
send2trash(file_path)

代码说明:

  1. 首先从send2trash库中导入核心功能函数send2trash,这是使用该库的唯一必要导入操作;
  2. 定义变量file_path,赋值为需要删除的文件名称或完整路径,支持相对路径与绝对路径;
  3. 直接调用send2trash()函数,将文件路径作为参数传入,执行后文件会立刻出现在系统回收站中。

os.remove()对比,代码结构几乎一致,学习成本极低,但安全性提升了数个等级。

3.2 使用绝对路径删除文件

在实际项目中,为了避免因工作目录变化导致的路径错误,推荐使用绝对路径进行文件操作,Send2Trash完美支持绝对路径。

示例代码:

from send2trash import send2trash

# Windows系统绝对路径示例
windows_file = r"C:\Users\Administrator\Desktop\demo.txt"
send2trash(windows_file)

# macOS/Linux系统绝对路径示例
unix_file = "/home/user/documents/report.pdf"
send2trash(unix_file)

代码说明:

  1. Windows路径中存在反斜杠,使用原生字符串r""可以避免转义字符问题;
  2. macOS与Linux使用正斜杠作为路径分隔符,直接书写路径即可;
  3. 无论使用哪种系统的绝对路径,send2trash()都能正常识别并执行操作。

3.3 删除文件夹(目录)

很多Python自带的文件删除函数,删除非空文件夹时会报错,需要先递归删除内部文件,而Send2Trash支持直接删除非空文件夹,自动处理目录内的所有子文件与子目录。

示例代码:

from send2trash import send2trash

# 定义要删除的文件夹路径
folder_path = "project_temp"

# 直接删除整个文件夹
send2trash(folder_path)

代码说明:

  1. 无论目标文件夹是否为空,send2trash()都可以正常处理;
  2. 文件夹会整体移动到回收站,内部结构保持完整,后续可从回收站完整恢复;
  3. 不需要像shutil.rmtree()那样永久删除目录,极大降低误删风险。

3.4 批量删除多个文件

在数据清理、日志整理、临时文件清理等场景中,经常需要批量删除多个文件,Send2Trash可以通过循环轻松实现批量操作。

示例代码:

from send2trash import send2trash

# 定义需要删除的文件列表
file_list = [
    "temp_1.log",
    "temp_2.log",
    "cache.dat",
    "old_data.xlsx"
]

# 循环删除列表中的所有文件
for file in file_list:
    send2trash(file)
    print(f"已将 {file} 移动到回收站")

代码说明:

  1. 创建包含多个文件路径的列表,集中管理需要删除的文件;
  2. 使用for循环遍历列表,逐个调用send2trash()函数;
  3. 配合print语句,可以清晰查看删除进度,方便调试与日志记录。

3.5 结合pathlib使用

在Python3中,pathlib是更现代化、更面向对象的路径处理模块,Send2Trash可以直接接收Path对象作为参数,兼容性极强。

示例代码:

from send2trash import send2trash
from pathlib import Path

# 创建Path对象
file_path = Path("data/result.csv")

# 直接传入Path对象
send2trash(file_path)

代码说明:

  1. 使用pathlib.Path创建路径对象,代码更简洁、可读性更高;
  2. send2trash()支持字符串路径与Path对象两种参数格式;
  3. 适合在现代化Python项目中使用,符合主流编码规范。

四、异常处理与安全机制

在实际使用过程中,文件可能不存在、权限不足、文件被占用等情况都会导致删除失败,为了让脚本更加健壮,建议结合异常处理机制使用Send2Trash。

4.1 基础异常捕获

from send2trash import send2trash

try:
    send2trash("missing_file.txt")
    print("文件已成功移动到回收站")
except FileNotFoundError:
    print("错误:文件不存在,无法执行操作")
except PermissionError:
    print("错误:权限不足,无法删除该文件")
except Exception as e:
    print(f"未知错误:{e}")

代码说明:

  1. 使用try-except捕获可能出现的异常;
  2. FileNotFoundError处理文件不存在的情况;
  3. PermissionError处理权限不足的问题;
  4. 通用Exception捕获其他未知异常,避免脚本直接崩溃。

4.2 批量删除时的异常处理

批量操作时,单个文件异常不应该影响整体流程,通过在循环内部捕获异常,可以实现跳过错误文件,继续执行后续删除任务。

from send2trash import send2trash

file_list = ["a.txt", "b.txt", "c.txt"]

for file in file_list:
    try:
        send2trash(file)
        print(f"成功:{file} 已移入回收站")
    except Exception as e:
        print(f"失败:{file} 处理出错,原因:{e}")

代码说明:

  1. 异常捕获放在循环内部,单个文件删除失败不会中断整个批量流程;
  2. 清晰区分成功与失败的文件,方便后续排查问题;
  3. 适用于自动化清理脚本、定时任务等无人值守场景。

五、实际应用案例

5.1 自动化清理项目临时文件

在开发项目时,经常会生成大量缓存文件、日志文件、临时编译文件,手动清理费时费力,使用Send2Trash可以编写一个安全清理脚本。

from send2trash import send2trash
import os

def clean_temp_files():
    # 定义需要清理的临时文件后缀
    temp_extensions = [".tmp", ".log", ".cache", ".bak"]

    # 遍历当前目录下的所有文件
    for filename in os.listdir("."):
        # 判断是否为临时文件
        if any(filename.endswith(ext) for ext in temp_extensions):
            try:
                send2trash(filename)
                print(f"清理临时文件:{filename}")
            except Exception as e:
                print(f"无法清理 {filename}:{e}")

if __name__ == "__main__":
    print("开始安全清理项目临时文件...")
    clean_temp_files()
    print("清理完成,文件均已移入回收站,可随时恢复")

代码说明:

  1. 定义需要清理的临时文件后缀,精准匹配目标文件;
  2. 遍历当前目录,筛选出符合条件的临时文件;
  3. 使用send2trash安全删除,而非直接永久删除;
  4. 脚本可重复运行,即使误清理也能从回收站恢复,安全可靠。

5.2 定时清理桌面过期文件

很多人习惯将临时文件放在桌面,时间久了会变得杂乱,结合Send2Trash可以打造一个桌面文件安全清理工具。

from send2trash import send2trash
import os
import time
from pathlib import Path

def clean_desktop_files(days=7):
    # 获取桌面路径
    desktop_path = Path.home() / "Desktop"
    # 获取当前时间
    now = time.time()

    # 遍历桌面文件
    for file in desktop_path.iterdir():
        if file.is_file():
            # 获取文件修改时间
            mtime = os.path.getmtime(file)
            # 计算文件存在天数
            days_old = (now - mtime) / (60 * 60 * 24)

            # 删除超过指定天数的文件
            if days_old > days:
                try:
                    send2trash(file)
                    print(f"已清理过期文件:{file.name}")
                except Exception as e:
                    print(f"清理失败:{file.name},{e}")

if __name__ == "__main__":
    # 清理桌面中超过7天的文件
    clean_desktop_files(days=7)

代码说明:

  1. 自动获取用户桌面路径,跨平台兼容;
  2. 根据文件修改时间判断是否过期;
  3. 只清理超过指定天数的文件,保留近期使用的文件;
  4. 所有文件均移入回收站,不会误删重要资料。

六、与传统删除方式对比

| 删除方式 | 是否永久删除 | 是否可恢复 | 跨平台 | 支持文件夹 | 适用场景 |
||-|–|–|||
| os.remove() | 是 | 否 | 是 | 否 | 确定无用的文件 |
| shutil.rmtree() | 是 | 否 | 是 | 是 | 目录彻底删除 |
| Send2Trash | 否 | 是 | 是 | 是 | 日常开发、自动化脚本、不确定是否需要的文件 |

从表格可以清晰看出,Send2Trash在安全性与便利性上全面领先传统删除方式,尤其适合开发调试、自动化脚本、文件批量处理等容易出现误操作的场景。

相关资源

  • Pypi地址:https://pypi.org/project/Send2Trash/
  • Github地址:https://github.com/arsenetar/send2trash
  • 官方文档地址:https://send2trash.readthedocs.io/

关注我,每天分享一个实用的Python自动化工具。

Python项目开发效率神器:pyscaffold从安装到实战全攻略

一、pyscaffold核心介绍

pyscaffold是一款专注于Python标准化项目搭建的工具库,可一键生成符合行业规范的项目结构,核心原理是通过模板引擎快速初始化工程文件,自动集成配置、测试、文档等模块。它遵循MIT开源许可,优点是降低项目搭建成本、统一团队开发规范,缺点是定制化修改需熟悉模板结构,对超小型脚本项目略显繁琐。

二、pyscaffold安装方法

pyscaffold支持pip直接安装,兼容Python3.7及以上版本,安装命令如下:

# 基础安装命令
pip install pyscaffold

# 升级到最新版本
pip install --upgrade pyscaffold

# 验证安装是否成功
putup --version

执行putup --version后,若终端输出版本号,说明安装完成,可正常使用。

三、pyscaffold基础使用流程

3.1 快速创建标准Python项目

pyscaffold核心命令为putup,直接使用项目名称即可生成完整工程:

# 创建名为my_project的标准Python项目
putup my_project

执行后会自动生成目录,包含源码、测试、配置、文档等文件,无需手动创建基础架构。

3.2 带Git初始化的项目创建

pyscaffold可自动关联Git,适合需要版本控制的项目:

# 创建项目并自动初始化Git仓库
putup --git my_git_project

该命令会生成.git文件夹,同时创建.gitignore文件,自动排除Python开发中无需提交的文件。

3.3 生成支持打包发布的项目

若需要将项目打包上传至PyPI,可添加打包相关参数:

# 创建可打包发布的Python项目
putup --package my_publish_project

自动生成setup.cfg、pyproject.toml等打包必需文件,简化发布流程。

3.4 自定义项目目录结构

pyscaffold支持自定义结构,满足不同开发场景需求:

# 创建包含CLI命令行工具的项目
putup --cli my_cli_project

# 创建包含数据科学相关配置的项目
putup --ds my_ds_project

不同参数对应不同场景,自动适配开发所需配置。

四、pyscaffold生成项目目录结构

4.1 标准项目目录展示

执行基础创建命令后,生成的标准结构如下:

my_project/
├── my_project/           # 主源码目录
│   ├── __init__.py       # 包标识文件
│   ├── __main__.py       # 程序入口
│   └── demo.py           # 业务逻辑文件
├── tests/                # 测试目录
│   ├── __init__.py
│   └── test_demo.py      # 测试用例
├── docs/                 # 文档目录
├── .gitignore            # Git忽略文件
├── pyproject.toml        # 项目配置
├── README.md             # 项目说明
└── setup.cfg             # 打包配置

4.2 目录功能说明

  • 主源码目录:存放核心业务代码,是项目运行基础
  • tests目录:自动集成测试框架,支持单元测试、集成测试
  • docs目录:用于生成项目说明文档,支持Sphinx
  • 配置文件:统一管理打包、依赖、运行参数

五、pyscaffold实战代码示例

5.1 基础功能代码编写

进入项目主目录,在demo.py中编写功能代码:

# my_project/demo.py
def say_hello(name: str) -> str:
    """
    生成问候语
    :param name: 用户名
    :return: 问候字符串
    """
    return f"Hello, {name}! 欢迎使用pyscaffold搭建的项目"

def calculate_sum(a: int, b: int) -> int:
    """
    两数求和
    :param a: 数字1
    :param b: 数字2
    :return: 和
    """
    return a + b

if __name__ == "__main__":
    print(say_hello("Python开发者"))
    print(f"10+20={calculate_sum(10,20)}")

该文件包含两个基础函数,用于测试项目运行环境。

5.2 测试用例编写

在tests目录中编写测试代码,验证功能准确性:

# tests/test_demo.py
from my_project.demo import say_hello, calculate_sum

def test_say_hello():
    # 测试问候函数
    assert say_hello("测试") == "Hello, 测试! 欢迎使用pyscaffold搭建的项目"

def test_calculate_sum():
    # 测试求和函数
    assert calculate_sum(5, 3) == 8
    assert calculate_sum(0, 0) == 0
    assert calculate_sum(-1, 1) == 0

pyscaffold自动适配pytest,无需额外配置测试环境。

5.3 项目运行与测试

执行运行和测试命令,检查项目是否正常工作:

# 运行主程序
python -m my_project

# 执行测试用例
pytest tests/ -v

运行后主程序输出结果,测试用例全部通过,说明项目结构稳定。

5.4 项目打包实战

使用自动生成的配置文件,快速打包项目:

# 安装打包工具
pip install build

# 执行打包
python -m build

打包完成后,生成dist目录,包含.tar.gz和.whl文件,可直接上传PyPI。

六、pyscaffold进阶使用技巧

6.1 集成虚拟环境

pyscaffold可配合虚拟环境使用,隔离项目依赖:

# 创建项目并自动生成虚拟环境
putup --venv my_venv_project

自动创建venv文件夹,激活后仅使用当前项目依赖。

6.2 自定义模板扩展

开发者可修改模板,适配团队规范:

# 导出默认模板
putup --export-template my_template

# 使用自定义模板创建项目
putup --template my_template my_custom_project

修改模板后,所有新项目统一使用自定义结构。

6.3 配合CI/CD使用

pyscaffold生成的项目支持自动化部署,可直接对接GitHub Actions等工具,自动执行测试、打包、发布流程,适合团队协作开发。

七、实际开发场景应用案例

7.1 小型工具类项目开发

使用pyscaffold搭建文件处理工具项目:

# 创建项目
putup file_tool_project

# 编写文件读取功能
# file_tool_project/file_tool_project/file_handler.py
def read_txt_file(file_path: str) -> str:
    with open(file_path, "r", encoding="utf-8") as f:
        return f.read()

def write_txt_file(file_path: str, content: str) -> None:
    with open(file_path, "w", encoding="utf-8") as f:
        f.write(content)

快速完成项目搭建,专注业务逻辑开发。

7.2 团队协作标准化项目

团队统一使用pyscaffold创建项目,避免目录混乱、配置缺失等问题,新成员可快速上手,降低沟通与维护成本。

7.3 开源项目快速发布

使用pyscaffold生成的项目,自带完整开源规范文件,包括LICENSE、README、测试文件,直接上传GitHub即可成为标准开源项目。

相关资源

  • Pypi地址:https://pypi.org/project/pyscaffold
  • Github地址:https://github.com/pyscaffold/pyscaffold
  • 官方文档地址:https://pyscaffold.org/

关注我,每天分享一个实用的Python自动化工具。

Python实用工具:wrapt 从入门到实战,优雅实现函数包装与装饰器

一、wrapt 库概述

wrapt 是一款专注于 Python 装饰器与函数包装的轻量级库,核心用于稳定、标准地实现函数、类、方法的包装,解决原生装饰器破坏函数元信息、签名异常、嵌套失效等问题。其基于描述符与调用协议工作,保留原对象属性,兼容性强、稳定性高,无额外依赖。该库采用 BSD 许可证,开源免费,适合生产环境使用,缺点是功能聚焦包装,无扩展能力。

二、wrapt 库安装方法

wrapt 支持 Python 3.6+ 所有版本,跨平台兼容,安装方式简洁,使用 pip 即可完成安装,命令如下:

pip install wrapt

安装完成后,可在 Python 交互环境中执行导入命令验证:

import wrapt
print(wrapt.__version__)

若能正常打印版本号,说明安装成功。该库体积极小,安装速度快,不会对项目环境造成额外负担,无论是小型脚本还是大型工程,都可放心引入。

三、wrapt 核心基础使用

3.1 基础装饰器实现

原生 Python 装饰器会导致被装饰函数的 __name____doc__ 等元信息丢失,调用签名异常,而 wrapt 可完美解决该问题。使用 @wrapt.decorator 装饰包装函数,即可创建标准装饰器。

import wrapt

# 定义基础装饰器
def simple_decorator(func):
    @wrapt.decorator
    def wrapper(wrapped, instance, args, kwargs):
        # 执行前逻辑
        print(f"函数 {wrapped.__name__} 即将执行")
        # 调用原函数
        result = wrapped(*args, **kwargs)
        # 执行后逻辑
        print(f"函数 {wrapped.__name__} 执行完成")
        return result
    return wrapper(func)

# 测试函数
@simple_decorator
def test_function(name):
    """这是一个测试函数"""
    print(f"Hello, {name}")

# 调用函数
test_function("wrapt")
# 查看元信息,未被破坏
print("函数名称:", test_function.__name__)
print("函数文档:", test_function.__doc__)

代码说明

  • wrapped 代表被包装的原函数,可直接调用并传递参数;
  • instance 为类方法中的实例,普通函数中为 None
  • argskwargs 分别为位置参数和关键字参数,完整传递给原函数;
  • 装饰后的函数保留原名称、文档字符串,解决原生装饰器痛点。

3.2 无侵入式函数包装

wrapt 支持不使用语法糖,直接对现有函数进行动态包装,适合对第三方库函数、内置函数进行增强,无需修改原函数代码。

import wrapt

# 定义包装逻辑
def log_wrapper(wrapped, instance, args, kwargs):
    print(f"[日志] 调用函数: {wrapped.__name__}")
    return wrapped(*args, **kwargs)

# 原函数
def add(a, b):
    return a + b

# 动态包装函数
wrapped_add = wrapt.wrap_function_wrapper(add, log_wrapper)

# 调用包装后的函数
print(add(1, 2))
print(wrapped_add(3, 4))

代码说明

  • wrapt.wrap_function_wrapper 可直接接收原函数和包装函数,返回新的包装对象;
  • 原函数不受影响,可同时使用原函数和包装后的函数,灵活适配不同场景。

3.3 类与实例方法包装

wrapt 对类方法、静态方法、实例方法的支持十分完善,会自动识别方法类型,正确传递 instance 参数,无需额外处理。

import wrapt

# 定义装饰器
def method_decorator(func):
    @wrapt.decorator
    def wrapper(wrapped, instance, args, kwargs):
        if instance is not None:
            print(f"类 {instance.__class__.__name__} 的方法执行")
        return wrapped(*args, **kwargs)
    return wrapper(func)

# 测试类
class TestClass:
    @method_decorator
    def instance_method(self):
        print("执行实例方法")

    @staticmethod
    @method_decorator
    def static_method():
        print("执行静态方法")

# 调用
obj = TestClass()
obj.instance_method()
TestClass.static_method()

代码说明

  • 实例方法中 instance 为类的实例对象,可通过其访问类属性与实例属性;
  • 静态方法中 instanceNone,装饰器逻辑可自动适配不同类型方法;
  • 无需为不同方法编写不同装饰器,一套逻辑通用。

3.4 带参数的装饰器实现

原生 Python 实现带参数的装饰器需要多层嵌套,代码可读性差,wrapt 可简化带参装饰器的编写,结构清晰易懂。

import wrapt

# 带参数的装饰器
def decorator_with_args(msg):
    @wrapt.decorator
    def wrapper(wrapped, instance, args, kwargs):
        print(f"自定义信息: {msg}")
        return wrapped(*args, **kwargs)
    return wrapper

# 使用带参装饰器
@decorator_with_args("这是自定义提示信息")
def demo_function():
    print("执行演示函数")

demo_function()

代码说明

  • 外层函数接收装饰器参数,内层通过 @wrapt.decorator 实现包装;
  • 无需多层嵌套函数,代码层级简洁,易于维护和扩展。

四、wrapt 高级功能使用

4.1 函数签名保留

在开发接口、工具库时,函数签名十分重要,原生装饰器会破坏 inspect 模块获取的签名,wrapt 可完整保留。

import wrapt
import inspect

# 装饰器
def signature_decorator(func):
    @wrapt.decorator
    def wrapper(wrapped, instance, args, kwargs):
        return wrapped(*args, **kwargs)
    return wrapper(func)

# 带签名的函数
@signature_decorator
def complex_func(a: int, b: str, c: list = None) -> dict:
    return {"a": a, "b": b, "c": c}

# 获取函数签名
print(inspect.signature(complex_func))

代码说明

  • 执行后可正常打印参数类型、默认值、返回值类型;
  • 适合开发 SDK、框架、对外接口,保证调用提示与文档准确性。

4.2 嵌套装饰器兼容

多个装饰器嵌套时,原生装饰器容易出现执行顺序混乱、元信息覆盖问题,wrapt 可保证嵌套稳定执行。

import wrapt

# 第一个装饰器
def decorator1(func):
    @wrapt.decorator
    def wrapper(wrapped, instance, args, kwargs):
        print("装饰器1 执行")
        return wrapped(*args, **kwargs)
    return wrapper(func)

# 第二个装饰器
def decorator2(func):
    @wrapt.decorator
    def wrapper(wrapped, instance, args, kwargs):
        print("装饰器2 执行")
        return wrapped(*args, **kwargs)
    return wrapper(func)

# 嵌套使用
@decorator1
@decorator2
def nested_func():
    print("原函数执行")

nested_func()

代码说明

  • 执行顺序严格遵循装饰器从上到下、原函数最后执行的规则;
  • 多层嵌套后元信息依然完整,无异常冲突。

4.3 类的整体包装

wrapt 不仅能包装方法,还能对整个类进行包装,批量增强类中所有方法,减少重复代码。

import wrapt

# 类包装器
def class_wrapper(wrapped, instance, args, kwargs):
    print(f"初始化类: {wrapped.__name__}")
    return wrapped(*args, **kwargs)

# 包装类
@wrapt.decorator
def log_class(wrapped, instance, args, kwargs):
    return class_wrapper(wrapped, instance, args, kwargs)

@log_class
class User:
    def __init__(self, name):
        self.name = name

user = User("test")

代码说明

  • 可对类的初始化、实例化过程进行拦截;
  • 适合日志记录、权限校验、参数验证等全局逻辑。

4.4 内置函数与第三方库包装

实际开发中常需增强内置函数或第三方库函数,wrapt 可在不修改源码的前提下完成包装。

import wrapt
import math

# 包装 math.sqrt 函数
original_sqrt = math.sqrt

@wrapt.decorator
def sqrt_wrapper(wrapped, instance, args, kwargs):
    num = args[0]
    print(f"计算平方根: {num}")
    if num < 0:
        raise ValueError("负数不能计算平方根")
    return wrapped(*args, **kwargs)

math.sqrt = sqrt_wrapper(math.sqrt)

# 测试
print(math.sqrt(16))
# print(math.sqrt(-4))  # 会触发异常

代码说明

  • 直接替换模块中的函数,对所有调用生效;
  • 可用于参数校验、异常捕获、性能统计、日志埋点等场景。

五、实际项目综合案例

5.1 接口请求耗时统计装饰器

在 Web 开发、接口调用场景中,经常需要统计函数执行耗时,使用 wrapt 可快速实现通用耗时统计装饰器。

import wrapt
import time

# 耗时统计装饰器
def time_counter(func):
    @wrapt.decorator
    def wrapper(wrapped, instance, args, kwargs):
        start_time = time.time()
        try:
            result = wrapped(*args, **kwargs)
            return result
        finally:
            end_time = time.time()
            cost_time = round((end_time - start_time) * 1000, 2)
            print(f"函数 {wrapped.__name__} 执行耗时: {cost_time} ms")
    return wrapper(func)

# 模拟接口请求
@time_counter
def request_api(url: str, timeout: int = 5) -> dict:
    """模拟接口请求函数"""
    time.sleep(0.5)
    return {"code": 200, "msg": "请求成功", "data": url}

# 调用
request_api("https://example.com/api")
# 查看元信息
print("函数名称:", request_api.__name__)
print("函数文档:", request_api.__doc__)

代码说明

  • 使用 try...finally 确保无论函数是否异常,都能统计耗时;
  • 装饰器可复用于所有需要耗时统计的函数,无侵入、无元信息丢失。

5.2 权限校验装饰器

在后端服务、管理系统中,权限校验是核心功能,使用 wrapt 可编写稳定的权限校验装饰器。

import wrapt

# 模拟用户权限数据
user_info = {
    "username": "admin",
    "is_admin": True
}

# 权限校验装饰器
def admin_required(func):
    @wrapt.decorator
    def wrapper(wrapped, instance, args, kwargs):
        if not user_info.get("is_admin"):
            raise PermissionError("无管理员权限,禁止访问")
        print("权限校验通过")
        return wrapped(*args, **kwargs)
    return wrapper(func)

# 管理员接口
@admin_required
def delete_user(user_id: int):
    print(f"删除用户: {user_id}")

# 调用
delete_user(1001)

代码说明

  • 装饰器独立于业务逻辑,可随时添加或移除;
  • 支持类方法、静态方法、普通函数,兼容性极强。

5.3 函数参数校验装饰器

在数据处理、脚本开发中,参数错误会导致程序崩溃,使用 wrapt 实现统一参数校验装饰器,提升代码健壮性。

import wrapt

# 参数校验装饰器
def param_check(func):
    @wrapt.decorator
    def wrapper(wrapped, instance, args, kwargs):
        # 校验位置参数
        for index, arg in enumerate(args):
            if not isinstance(arg, (int, str)):
                raise TypeError(f"第 {index} 个参数类型错误")
        # 校验关键字参数
        for key, value in kwargs.items():
            if value is None:
                raise ValueError(f"参数 {key} 不能为空")
        return wrapped(*args, **kwargs)
    return wrapper(func)

@param_check
def process_data(a, b, c=None):
    print(f"处理数据: {a}, {b}, {c}")

process_data(1, "test", c="data")
# process_data(None, [], c=None)  # 触发参数异常

代码说明

  • 统一封装校验逻辑,避免每个函数重复编写判断代码;
  • 包装后函数签名不变,不影响调用提示。

六、相关资源

  • Pypi地址:https://pypi.org/project/wrapt/
  • Github地址:https://github.com/GrahamDumpleton/wrapt
  • 官方文档地址:https://wrapt.readthedocs.io/

关注我,每天分享一个实用的Python自动化工具。

Python实用工具:decorator库详解——优雅实现装饰器,告别函数签名丢失

一、decorator库概述

decorator是Python中专门用于简化装饰器编写的第三方库,核心作用是帮助开发者快速创建标准、规范的装饰器,自动保留被装饰函数的元信息(如函数名、文档字符串、参数签名),解决原生装饰器容易丢失函数元数据的问题。其原理是通过封装包装逻辑,自动完成functools.wraps的底层操作,降低装饰器编写门槛。该库轻量无侵入,使用简单,适合所有Python版本,License为BSD许可。优点是编写简洁、兼容性强、自动保留函数信息,缺点是仅专注装饰器场景,功能单一。

二、decorator库安装方法

decorator作为PyPI上的标准第三方库,可通过pip命令快速安装,兼容Python 2.7至Python 3.12+所有主流版本,安装命令如下:

pip install decorator

安装完成后,可在Python交互环境中执行导入命令验证是否安装成功:

import decorator
print(decorator.__version__)

若正常输出版本号,说明安装无误,可直接在项目中使用。

三、decorator库基础使用详解

3.1 原生装饰器的痛点

在不使用decorator库时,Python原生装饰器需要手动使用functools.wraps修饰包装函数,否则被装饰后的函数会丢失原有的__name____doc__、参数签名等信息,导致调试、文档生成、反射操作出现异常。

原生装饰器示例:

import functools

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("执行函数前")
        result = func(*args, **kwargs)
        print("执行函数后")
        return result
    return wrapper

@my_decorator
def add(a, b):
    """两数相加函数"""
    return a + b

print(add.__name__)  # 输出 wrapper,而非 add
print(add.__doc__)   # 输出 None,丢失文档字符串

可以看到,被装饰后的函数add的名称和文档字符串都被替换成了包装函数wrapper的信息,这在实际开发中会带来诸多不便。

3.2 使用decorator编写基础装饰器

decorator库提供了decorator装饰器,只需将包装函数作为参数传入,即可自动保留被装饰函数的所有元信息,无需手动处理functools.wraps

基础使用示例:

from decorator import decorator

@decorator
def my_decorator(func, *args, **kwargs):
    print("函数执行前操作")
    result = func(*args, **kwargs)
    print("函数执行后操作")
    return result

@my_decorator
def add(a, b):
    """两数相加函数"""
    return a + b

# 调用函数
print(add(2, 3))
# 查看函数元信息
print(add.__name__)  # 输出 add,保留原函数名
print(add.__doc__)   # 输出 两数相加函数,保留文档字符串

代码说明:

  1. 导入decorator库中的核心装饰器函数;
  2. @decorator修饰自定义装饰器函数,函数参数固定为func, *args, **kwargs
  3. func代表被装饰的原函数,*args, **kwargs接收原函数的所有参数;
  4. 直接在装饰器函数中编写前置、后置逻辑,最后返回原函数的执行结果;
  5. 被装饰后的函数完整保留原有的名称、文档字符串、参数签名。

执行结果:

函数执行前操作
函数执行后操作
5
add
两数相加函数

3.3 带参数的装饰器实现

decorator库支持编写带参数的装饰器,只需在基础装饰器外层再包裹一层函数,用于接收自定义参数,使用方式更加灵活。

带参数装饰器示例:

from decorator import decorator

def log(level="info"):
    @decorator
    def log_decorator(func, *args, **kwargs):
        print(f"[{level.upper()}] 执行函数: {func.__name__}")
        result = func(*args, **kwargs)
        print(f"[{level.upper()}] 函数执行完成")
        return result
    return log_decorator

# 使用默认参数
@log()
def multiply(a, b):
    """两数相乘函数"""
    return a * b

# 使用自定义参数
@log(level="error")
def divide(a, b):
    """两数相除函数"""
    return a / b

# 调用函数
print(multiply(4, 5))
print(divide(10, 2))

代码说明:

  1. 定义外层函数log,接收装饰器参数level,默认值为info
  2. 内层使用@decorator修饰实际的装饰器逻辑;
  3. 外层函数返回内层装饰器,实现参数传递;
  4. 调用装饰器时可传入自定义参数,控制装饰器的行为。

执行结果:

[INFO] 执行函数: multiply
[INFO] 函数执行完成
20
[ERROR] 执行函数: divide
[ERROR] 函数执行完成
5.0

3.4 装饰类方法

decorator库不仅支持装饰普通函数,还能完美兼容类的实例方法、类方法、静态方法,无需额外修改代码,适配面向对象开发场景。

装饰类方法示例:

from decorator import decorator

@decorator
def time_record(func, *args, **kwargs):
    import time
    start = time.time()
    result = func(*args, **kwargs)
    end = time.time()
    print(f"函数 {func.__name__} 执行耗时: {end - start:.4f}s")
    return result

class Calculator:
    """计算器类"""

    @time_record
    def add(self, a, b):
        return a + b

    @classmethod
    @time_record
    def multiply(cls, a, b):
        return a * b

    @staticmethod
    @time_record
    def subtract(a, b):
        return a - b

# 创建实例并调用
calc = Calculator()
print(calc.add(10, 20))
print(Calculator.multiply(5, 6))
print(Calculator.subtract(30, 10))

代码说明:

  1. 定义通用的耗时统计装饰器time_record
  2. 分别装饰实例方法、类方法、静态方法;
  3. 装饰器无需区分方法类型,自动适配所有类方法场景;
  4. 执行后可正常输出函数耗时,且保留类方法的元信息。

四、decorator库高级用法

4.1 保留函数参数签名

在原生装饰器中,即使使用functools.wraps,也可能无法完整保留函数的参数签名,而decorator库可以完美解决这个问题,让inspect模块能正确获取函数参数信息。

参数签名保留示例:

from decorator import decorator
import inspect

@decorator
def simple_decorator(func, *args, **kwargs):
    return func(*args, **kwargs)

@simple_decorator
def user_info(name: str, age: int, gender: str = "male") -> str:
    """获取用户信息"""
    return f"姓名:{name}, 年龄:{age}, 性别:{gender}"

# 获取函数签名
sig = inspect.signature(user_info)
print("函数参数签名:", sig)
# 获取函数注解
print("函数参数注解:", user_info.__annotations__)

代码说明:

  1. 使用inspect.signature获取被装饰函数的参数签名;
  2. decorator库完整保留了参数名称、默认值、类型注解;
  3. 原生装饰器无法实现这种完整的参数签名保留效果。

执行结果:

函数参数签名: (name: str, age: int, gender: str = 'male') -> str
函数参数注解: {'name': <class 'str'>, 'age': <class 'int'>, 'gender': <class 'str'>, 'return': <class 'str'>}

4.2 叠加多个装饰器

decorator库支持多个装饰器叠加使用,执行顺序与原生装饰器一致,且所有装饰器都能正常保留函数元信息,无冲突问题。

多装饰器叠加示例:

from decorator import decorator

@decorator
def decorator1(func, *args, **kwargs):
    print("装饰器1前置")
    res = func(*args, **kwargs)
    print("装饰器1后置")
    return res

@decorator
def decorator2(func, *args, **kwargs):
    print("装饰器2前置")
    res = func(*args, **kwargs)
    print("装饰器2后置")
    return res

@decorator1
@decorator2
def test_func():
    print("执行原函数")

test_func()
print("函数名:", test_func.__name__)

执行结果:

装饰器1前置
装饰器2前置
执行原函数
装饰器2后置
装饰器1后置
函数名: test_func

4.3 无侵入式装饰现有函数

除了使用@语法糖装饰函数外,decorator库还支持无侵入式地为现有函数添加装饰器,无需修改原函数定义,适合对已有项目进行扩展。

无侵入式装饰示例:

from decorator import decorator

@decorator
def log_decorator(func, *args, **kwargs):
    print(f"调用函数: {func.__name__}")
    return func(*args, **kwargs)

# 定义原始函数
def hello(name):
    return f"Hello, {name}"

# 无侵入式添加装饰器
hello = log_decorator(hello)

# 调用装饰后的函数
print(hello("Python"))

执行结果:

调用函数: hello
Hello, Python

五、实际开发案例

5.1 接口请求重试装饰器

在网络请求、接口调用场景中,经常需要实现失败重试功能,使用decorator库可以快速编写通用的重试装饰器,适配所有请求函数。

from decorator import decorator
import time
import requests

def retry(max_retry=3, delay=1):
    @decorator
    def retry_decorator(func, *args, **kwargs):
        for i in range(max_retry):
            try:
                return func(*args, **kwargs)
            except Exception as e:
                print(f"第{i+1}次执行失败,错误信息: {e},{delay}秒后重试")
                time.sleep(delay)
        raise Exception(f"重试{max_retry}次后仍执行失败")
    return retry_decorator

# 使用重试装饰器装饰请求函数
@retry(max_retry=2, delay=0.5)
def get_api_data(url):
    """请求接口数据"""
    response = requests.get(url, timeout=3)
    response.raise_for_status()
    return response.json()

# 调用函数
try:
    data = get_api_data("https://api.github.com")
    print("请求成功:", data)
except Exception as e:
    print("最终请求失败:", e)

5.2 权限校验装饰器

在Web开发、接口服务中,权限校验是常用功能,使用decorator库编写权限校验装饰器,可快速实现接口权限控制。

from decorator import decorator

user_permissions = ["read", "write"]

def require_permission(permission):
    @decorator
    def permission_decorator(func, *args, **kwargs):
        if permission not in user_permissions:
            raise PermissionError(f"缺少{permission}权限,无法执行操作")
        print(f"权限{permission}校验通过")
        return func(*args, **kwargs)
    return permission_decorator

@require_permission("write")
def create_data(data):
    """创建数据"""
    return f"创建数据成功: {data}"

@require_permission("delete")
def delete_data(data_id):
    """删除数据"""
    return f"删除数据{data_id}成功"

# 调用有权限的函数
print(create_data({"id": 1, "name": "test"}))

# 调用无权限的函数
try:
    delete_data(1)
except PermissionError as e:
    print(e)

相关资源

  • Pypi地址:https://pypi.org/project/decorator/
  • Github地址:https://github.com/micheles/decorator
  • 官方文档地址:https://decorator.readthedocs.io/

关注我,每天分享一个实用的Python自动化工具。

Python插件化开发利器:pluggy从入门到实战详解

一、pluggy库概述

pluggy是一款专为Python打造的轻量级插件化开发框架,核心用于构建可扩展、松耦合的程序架构,通过钩子机制实现主程序与插件的解耦。其工作原理基于钩子函数与插件注册,主程序定义钩子规范,插件实现钩子逻辑,由pluggy完成自动发现与调用。该库采用MIT许可,优点是轻量无侵入、扩展性强、适配各类Python项目,缺点是仅专注插件核心逻辑,无配套UI与复杂管理功能。

二、pluggy安装与基础环境配置

在正式使用pluggy之前,需要先完成库的安装操作,pluggy支持pip一键安装,兼容Python3.6及以上版本,几乎适配所有主流操作系统与Python环境。

打开命令行终端,执行以下安装命令:

pip install pluggy

安装完成后,可以通过简单的导入语句验证是否安装成功,若未报错则说明环境配置完成:

import pluggy
print(pluggy.__version__)

上述代码用于导入pluggy库并打印其版本号,确认安装无误后,即可进入插件化开发的核心环节。pluggy无需额外配置文件,也不依赖其他第三方库,轻量化特性使其可以快速集成到现有项目中,不会增加项目的依赖负担。

三、pluggy核心概念与工作流程

想要熟练使用pluggy,必须先理解其核心概念,这些概念是构建插件系统的基础,掌握后可以快速搭建稳定的插件架构。

3.1 核心概念解析

  1. 钩子规范(Hook Specification)
    钩子规范是主程序定义的接口标准,规定了钩子函数的名称、参数、返回值要求,相当于插件需要遵循的“协议”,所有插件都必须按照这个规范实现对应的逻辑。
  2. 钩子实现(Hook Implementation)
    钩子实现是插件开发者根据钩子规范编写的具体业务代码,一个钩子规范可以对应多个钩子实现,pluggy会按照设定的顺序执行这些实现。
  3. 插件管理器(PluginManager)
    插件管理器是pluggy的核心组件,负责注册插件、收集钩子规范、管理钩子实现、调度钩子执行,是连接主程序与插件的核心枢纽。
  4. 钩子调用(Hook Call)
    主程序在合适的时机调用钩子,插件管理器会自动遍历所有已注册的插件,执行对应的钩子实现,并返回执行结果。

3.2 基础工作流程

pluggy的工作流程清晰简洁,分为四个核心步骤:

  1. 创建插件管理器实例;
  2. 定义钩子规范并注册到管理器;
  3. 编写插件实现钩子函数,并将插件注册到管理器;
  4. 主程序调用钩子,触发插件逻辑执行。

整个流程实现了主程序与插件的完全解耦,主程序无需知道插件的具体实现,插件也无需修改主程序代码,极大提升了项目的可扩展性。

四、pluggy基础使用教程与代码示例

本节通过从零开始的简单案例,逐步演示pluggy的基础用法,让初学者快速掌握核心使用方式。

4.1 最简单的插件系统实现

首先搭建一个最基础的插件系统,包含主程序定义钩子、插件实现钩子、调用钩子三个环节,代码简洁易懂,适合入门学习。

# 导入pluggy核心模块
import pluggy

# 1. 定义钩子规范
class HooksSpec:
    # 声明钩子规范,指定钩子名称为hello
    @pluggy.hookspec
    def hello(self):
        pass

# 2. 编写插件,实现钩子
class MyPlugin:
    # 钩子实现,与规范名称保持一致
    @pluggy.hookimpl
    def hello(self):
        print("欢迎使用pluggy插件系统!")

# 3. 创建插件管理器并配置
pm = pluggy.PluginManager("demo_plugin")
# 注册钩子规范
pm.add_hookspecs(HooksSpec)
# 注册插件
pm.register(MyPlugin())

# 4. 调用钩子,触发插件执行
pm.hook.hello()

代码说明

  • 首先定义HooksSpec类,通过@pluggy.hookspec装饰器声明hello钩子规范,规定插件需要实现的接口;
  • MyPlugin是自定义插件,通过@pluggy.hookimpl装饰器实现hello钩子的具体逻辑;
  • 创建PluginManager实例,传入插件系统名称,先注册钩子规范,再注册插件;
  • 最后通过pm.hook.hello()调用钩子,自动执行插件中的实现逻辑。

运行代码后,控制台会输出欢迎使用pluggy插件系统!,证明基础插件系统搭建成功。

4.2 带参数的钩子实现

实际开发中,钩子函数通常需要传递参数,本节演示带参数的钩子规范与实现,适配更多业务场景。

import pluggy

# 定义带参数的钩子规范
class ParamHooksSpec:
    @pluggy.hookspec
    def greet(self, name):
        """
        问候钩子
        :param name: 用户名
        """
        pass

# 插件实现带参数的钩子
class GreetPlugin:
    @pluggy.hookimpl
    def greet(self, name):
        print(f"你好,{name}!")

# 多个插件可以实现同一个钩子
class AnotherGreetPlugin:
    @pluggy.hookimpl
    def greet(self, name):
        print(f"Hello, {name}!")

# 初始化管理器
pm = pluggy.PluginManager("param_demo")
pm.add_hookspecs(ParamHooksSpec)

# 注册多个插件
pm.register(GreetPlugin())
pm.register(AnotherGreetPlugin())

# 调用钩子并传入参数
pm.hook.greet(name="Python开发者")

代码说明

  • 钩子规范中定义了带name参数的greet函数,插件必须按照参数格式实现;
  • 可以注册多个插件实现同一个钩子,调用时所有实现都会被执行;
  • 调用钩子时通过关键字参数传递数据,保证参数传递的稳定性。

运行代码后,会依次输出两个插件的问候语,体现了pluggy多插件扩展的特性。

4.3 带返回值的钩子使用

除了无返回值、带参数的钩子,pluggy还支持钩子返回数据,主程序可以接收并处理插件的返回结果。

import pluggy

# 定义带返回值的钩子规范
class ReturnHooksSpec:
    @pluggy.hookspec
    def calculate(self, num1, num2):
        pass

# 插件1:实现加法计算
class AddPlugin:
    @pluggy.hookimpl
    def calculate(self, num1, num2):
        return num1 + num2

# 插件2:实现乘法计算
class MultiplyPlugin:
    @pluggy.hookimpl
    def calculate(self, num1, num2):
        return num1 * num2

# 配置管理器
pm = pluggy.PluginManager("return_demo")
pm.add_hookspecs(ReturnHooksSpec)
pm.register(AddPlugin())
pm.register(MultiplyPlugin())

# 调用钩子并获取返回值
results = pm.hook.calculate(num1=10, num2=5)
print("所有插件执行结果:", results)

代码说明

  • 钩子实现中通过return返回计算结果;
  • 多个插件的返回值会以列表形式返回,主程序可以遍历处理;
  • 该特性适合数据处理、结果聚合等业务场景。

运行代码后,输出所有插件执行结果: [15, 50],分别对应加法与乘法的计算结果。

五、pluggy高级功能使用

掌握基础用法后,本节介绍pluggy的高级功能,包括钩子执行顺序控制、钩子过滤、插件分组、动态加载插件等,满足复杂项目的开发需求。

5.1 控制钩子执行顺序

pluggy支持通过tryfirsttrylasthookwrapper参数控制钩子执行顺序,解决多插件执行优先级问题。

import pluggy

class OrderHooksSpec:
    @pluggy.hookspec
    def process(self):
        pass

class FirstPlugin:
    # 优先执行
    @pluggy.hookimpl(tryfirst=True)
    def process(self):
        print("第一步:数据初始化")

class LastPlugin:
    # 最后执行
    @pluggy.hookimpl(trylast=True)
    def process(self):
        print("第三步:数据保存")

class MiddlePlugin:
    @pluggy.hookimpl
    def process(self):
        print("第二步:数据处理")

# 配置并调用
pm = pluggy.PluginManager("order_demo")
pm.add_hookspecs(OrderHooksSpec)
pm.register(FirstPlugin())
pm.register(MiddlePlugin())
pm.register(LastPlugin())
pm.hook.process()

代码说明

  • tryfirst=True表示该钩子实现优先执行;
  • trylast=True表示该钩子实现最后执行;
  • 未设置参数的钩子实现按注册顺序执行,实现灵活的顺序控制。

运行结果严格按照“初始化→处理→保存”的顺序输出,符合业务逻辑要求。

5.2 钩子包装器(Hook Wrapper)

钩子包装器可以在钩子执行前后添加自定义逻辑,类似装饰器模式,用于日志记录、异常捕获、性能统计等场景。

import pluggy
import time

class WrapperHooksSpec:
    @pluggy.hookspec
    def work(self):
        pass

class WorkPlugin:
    @pluggy.hookimpl
    def work(self):
        time.sleep(1)
        print("核心业务执行完成")

# 包装器插件
class WrapperPlugin:
    @pluggy.hookimpl(hookwrapper=True)
    def work(self):
        # 执行前逻辑
        start_time = time.time()
        print("开始执行业务,记录时间")
        # 触发实际钩子执行
        yield
        # 执行后逻辑
        end_time = time.time()
        print(f"业务执行结束,耗时:{end_time - start_time:.2f}秒")

# 配置管理器
pm = pluggy.PluginManager("wrapper_demo")
pm.add_hookspecs(WrapperHooksSpec)
pm.register(WorkPlugin())
pm.register(WrapperPlugin())
pm.hook.work()

代码说明

  • hookwrapper=True声明该钩子为包装器;
  • 通过yield分割执行前与执行后的逻辑;
  • 无需修改核心业务代码,即可添加通用功能,符合开闭原则。

运行代码后,会先记录开始时间,执行业务逻辑,最后统计并输出耗时。

5.3 动态加载插件

pluggy支持动态发现并加载插件,无需手动注册,适合插件数量多、需要动态扩展的项目。

import pluggy
import os
import importlib.util

# 定义钩子规范
class DynamicHooksSpec:
    @pluggy.hookspec
    def dynamic_task(self):
        pass

# 动态加载插件函数
def load_plugin_from_file(plugin_path):
    spec = importlib.util.spec_from_file_location("dynamic_plugin", plugin_path)
    plugin = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(plugin)
    return plugin

# 初始化管理器
pm = pluggy.PluginManager("dynamic_demo")
pm.add_hookspecs(DynamicHooksSpec)

# 模拟动态加载当前目录下的插件文件
plugin_file = "dynamic_plugin.py"
if os.path.exists(plugin_file):
    plugin = load_plugin_from_file(plugin_file)
    pm.register(plugin)
    pm.hook.dynamic_task()
else:
    print("插件文件不存在")

同时创建dynamic_plugin.py插件文件:

import pluggy

class DynamicPlugin:
    @pluggy.hookimpl
    def dynamic_task(self):
        print("动态插件执行成功!")

# 实例化插件
plugin = DynamicPlugin()

代码说明

  • 通过文件路径动态导入插件模块,实现插件的热加载;
  • 主程序无需提前知道插件的具体实现,只需遵循钩子规范;
  • 适合插件可插拔、可替换的项目场景。

六、pluggy实际项目应用案例

本节结合真实开发场景,实现一个基于pluggy的数据处理插件系统,模拟数据分析工具的功能扩展,涵盖数据读取、数据清洗、数据统计、数据导出全流程,完全贴合实际开发需求。

6.1 项目需求

开发一个通用数据处理工具,支持通过插件扩展数据处理功能,主程序负责流程调度,插件实现具体处理逻辑,包括:

  1. CSV数据读取插件;
  2. 缺失值清洗插件;
  3. 数据统计插件;
  4. 结果导出插件。

6.2 完整代码实现

import pluggy
import pandas as pd
import numpy as np

# 一、定义钩子规范
class DataProcessHooks:
    @pluggy.hookspec
    def read_data(self, file_path):
        """读取数据"""
        pass

    @pluggy.hookspec
    def clean_data(self, data):
        """清洗数据"""
        pass

    @pluggy.hookspec
    def analyze_data(self, data):
        """数据分析"""
        pass

    @pluggy.hookspec
    def export_result(self, result):
        """导出结果"""
        pass

# 二、实现各类插件
# 1. CSV读取插件
class CSVReadPlugin:
    @pluggy.hookimpl
    def read_data(self, file_path):
        print("CSV插件:正在读取数据...")
        return pd.read_csv(file_path)

# 2. 缺失值清洗插件
class CleanPlugin:
    @pluggy.hookimpl
    def clean_data(self, data):
        print("清洗插件:处理缺失值...")
        return data.dropna()

# 3. 数据统计插件
class AnalyzePlugin:
    @pluggy.hookimpl
    def analyze_data(self, data):
        print("统计插件:生成数据报告...")
        return data.describe()

# 4. 控制台导出插件
class ConsoleExportPlugin:
    @pluggy.hookimpl
    def export_result(self, result):
        print("导出插件:控制台输出结果")
        print(result)

# 三、主程序流程
class DataProcessTool:
    def __init__(self):
        # 初始化插件管理器
        self.pm = pluggy.PluginManager("data_process")
        self.pm.add_hookspecs(DataProcessHooks)
        # 注册所有插件
        self.pm.register(CSVReadPlugin())
        self.pm.register(CleanPlugin())
        self.pm.register(AnalyzePlugin())
        self.pm.register(ConsoleExportPlugin())

    def run(self, file_path):
        # 1. 读取数据
        data = self.pm.hook.read_data(file_path=file_path)[0]
        # 2. 清洗数据
        clean_data = self.pm.hook.clean_data(data=data)[0]
        # 3. 数据分析
        result = self.pm.hook.analyze_data(data=clean_data)[0]
        # 4. 导出结果
        self.pm.hook.export_result(result=result)

# 四、运行项目
if __name__ == "__main__":
    # 模拟测试数据
    test_data = pd.DataFrame({
        "id": [1, 2, np.nan, 4, 5],
        "value": [10, 20, 30, np.nan, 50]
    })
    test_data.to_csv("test_data.csv", index=False)

    # 启动数据处理工具
    tool = DataProcessTool()
    tool.run("test_data.csv")

6.3 案例说明

该案例完整模拟了实际项目中的插件化架构,主程序DataProcessTool只负责流程调度,所有具体功能都由插件实现:

  • 新增数据处理功能时,只需编写新插件并注册,无需修改主程序;
  • 替换功能时,只需注销旧插件、注册新插件,不影响系统稳定性;
  • 团队协作中,不同开发者可以独立开发插件,降低耦合与冲突风险。

这个架构可以直接应用于数据分析工具、爬虫系统、自动化脚本、Web框架等各类Python项目,是pluggy最具实用价值的应用场景。

七、pluggy适用场景与优势总结

pluggy凭借轻量、灵活、无侵入的特性,适用于众多Python开发场景:

  1. 框架开发:如pytest、tox等知名Python框架均使用pluggy实现插件系统;
  2. 自动化工具:桌面自动化、运维脚本、数据处理工具的功能扩展;
  3. 可扩展应用:需要支持第三方插件、自定义功能的软件项目;
  4. 模块化项目:需要解耦业务模块,提升代码可维护性的项目。

相比其他插件化框架,pluggy无需复杂配置、无强制代码规范、不侵入业务逻辑,学习成本极低,同时支持钩子顺序、包装器、动态加载等高级功能,兼顾简洁性与实用性,是Python插件化开发的首选工具。

相关资源

  • Pypi地址:https://pypi.org/project/pluggy/
  • Github地址:https://github.com/pytest-dev/pluggy
  • 官方文档地址:https://pluggy.readthedocs.io/

关注我,每天分享一个实用的Python自动化工具。

Python 项目模板神器:Copier 从入门到实战,一键生成标准化工程

一、Copier 库概述

Copier 是一款基于 Python 开发的项目模板生成与同步工具,核心作用是通过模板仓库快速创建标准化项目,同时支持模板更新后同步到已有项目,无需手动重构代码。其原理是通过解析模板目录、变量配置与渲染规则,结合 Jinja2 模板引擎完成文件生成,可跨平台使用。优点是支持增量更新、模板变量灵活、兼容 Git 仓库、配置简洁;缺点是复杂模板需掌握 Jinja2 语法,对超大型项目的定制化适配略繁琐。该库采用 MIT 开源许可证,可自由商用、修改与分发。

二、Copier 安装与基础环境配置

2.1 环境要求

Copier 对 Python 环境兼容性较好,支持 Python 3.7 及以上版本,同时依赖 Git 工具用于拉取远程模板仓库,使用前需确保本地已安装 Python 与 Git。

2.2 安装命令

Copier 可通过 pip 直接安装,打开命令行工具(CMD、PowerShell、终端均可),执行以下命令:

pip install copier

安装完成后,可通过版本检查命令验证是否安装成功:

copier --version

若输出对应的版本号(如 9.3.0),则说明安装正常,可直接使用。

2.3 基础命令介绍

Copier 的核心命令简洁易懂,新手也能快速上手,常用命令如下:

  • copier copy:从模板仓库复制生成新项目
  • copier update:将模板更新同步到已有项目
  • copier inspect:查看模板的变量配置信息
  • copier --help:查看完整命令帮助文档

三、Copier 核心使用流程与基础示例

3.1 快速生成第一个项目

Copier 支持本地模板与远程 Git 模板两种使用方式,先以远程公共模板为例,演示最基础的项目生成流程。以 Python 基础项目模板为例,打开命令行,进入需要创建项目的目录,执行命令:

copier copy https://github.com/copier-org/copier-template-demo demo_project

命令解释:

  • copier copy:核心生成命令
  • https://github.com/copier-org/copier-template-demo:远程模板仓库地址
  • demo_project:本地生成的项目文件夹名称

执行后,Copier 会交互式询问项目名称、作者、版本号等基础信息,按提示输入即可,等待几秒后,demo_project 文件夹就会生成完整的 Python 项目结构,包含 README.mdpyproject.toml、示例代码等标准化文件,无需手动创建。

3.2 本地模板使用示例

除了远程模板,Copier 更适合自定义本地模板,适配团队或个人的项目规范。首先创建一个本地模板目录,结构如下:

my_python_template/
├── copier.yml          # Copier 配置文件,定义模板变量
├── [[project_name]]/   # 动态项目名称目录
│   ├── __init__.py
│   └── main.py         # 核心业务代码
└── README.md.jinja2    # 带模板变量的说明文件

第一步,编写 Copier 核心配置文件 copier.yml,用于定义交互式提问的变量:

# copier.yml
project_name:
  type: str
  help: 请输入项目名称
  default: my_python_app

author:
  type: str
  help: 请输入作者名称
  default: 匿名开发者

version:
  type: str
  help: 项目初始版本
  default: 0.1.0

该配置文件会在生成项目时,自动向用户提问,收集变量值用于渲染模板。

第二步,编写模板文件,以 README.md.jinja2 为例,使用 Jinja2 语法插入变量:

# {{ project_name }}
作者:{{ author }}
版本:{{ version }}

## 项目介绍
这是使用 Copier 生成的标准化 Python 项目。

第三步,编写动态目录下的 main.py 模板文件:

# [[project_name]]/main.py
def main():
    print("Hello from {{ project_name }}!")

if __name__ == "__main__":
    main()

第四步,使用本地模板生成项目,执行命令:

copier copy ./my_python_template my_new_project

按提示输入项目名称、作者、版本后,Copier 会自动替换所有模板变量,生成完整的项目结构,打开 my_new_project 即可看到变量已被替换为实际内容,main.py 可直接运行。

3.3 模板更新同步(核心优势)

传统模板工具生成项目后,模板更新无法同步到已有项目,而 Copier 可通过 update 命令实现增量同步,这是其最核心的优势。

假设之前生成的 my_new_project 项目,现在本地模板 my_python_template 新增了日志模块、配置文件,修改了 README.md 格式,只需进入项目目录,执行:

cd my_new_project
copier update

Copier 会自动对比模板与现有项目的差异,只更新变更的文件,不会覆盖用户手动编写的业务代码,支持冲突提示与手动合并,完美解决项目长期维护中模板升级的痛点。

四、Copier 进阶功能与实战案例

4.1 复杂变量配置

Copier 支持多种变量类型,包括字符串、布尔值、列表、选择项等,可满足复杂项目的模板定制需求。修改 copier.yml 配置复杂变量:

project_name:
  type: str
  help: 项目名称
  default: python_app

use_database:
  type: bool
  help: 是否使用数据库
  default: false

db_type:
  type: str
  help: 数据库类型
  choices:
    - mysql
    - sqlite
    - postgresql
  default: sqlite
  when: use_database  # 仅当 use_database 为 true 时显示提问

使用该模板时,若选择不使用数据库,则不会询问数据库类型,逻辑更灵活。

4.2 条件生成文件

Copier 支持根据变量条件,决定是否生成某个文件或目录。例如,仅当用户选择使用数据库时,才生成数据库配置文件 db_config.py.jinja2

# {% if use_database %}
# db_config.py
DB_TYPE = "{{ db_type }}"
DB_PATH = "data/db.sqlite3"
# {% endif %}

use_databasefalse 时,该文件不会生成,实现按需创建项目文件。

4.3 结合 Git 远程模板团队协作

在团队开发中,可将统一模板上传到 Git 仓库,所有成员通过远程地址生成项目,保证项目结构标准化。例如将模板推送到 GitHub 后,团队成员执行:

copier copy [email protected]:team/team-python-template.git team_project

模板更新后,成员只需在项目内执行 copier update,即可同步团队最新规范,无需人工统一调整项目结构。

4.4 完整可运行实战案例

下面搭建一个完整的 Python Web 项目模板,包含 Flask 框架、配置文件、路由文件、静态目录,实现一键生成可运行的 Web 项目。

模板目录结构:

flask_copier_template/
├── copier.yml
├── [[project_name]]/
│   ├── __init__.py
│   ├── app.py
│   ├── config.py
│   ├── static/
│   └── templates/
└── README.md.jinja2

编写 copier.yml

project_name:
  type: str
  help: Flask 项目名称
  default: flask_app

host:
  type: str
  help: 运行主机
  default: 127.0.0.1

port:
  type: int
  help: 运行端口
  default: 5000

debug:
  type: bool
  help: 是否开启调试模式
  default: true

编写 app.py.jinja2

from flask import Flask
from config import Config

app = Flask(__name__)
app.config.from_object(Config)

@app.route('/')
def index():
    return "<h1>欢迎使用 {{ project_name }}!</h1>"

if __name__ == '__main__':
    app.run(
        host="{{ host }}",
        port={{ port }},
        debug={{ debug }}
    )

编写 config.py.jinja2

class Config:
    SECRET_KEY = "copier_flask_template_key"
    DEBUG = {{ debug }}

使用该模板生成项目:

copier copy ./flask_copier_template my_flask_app

进入项目目录,安装 Flask 后直接运行:

cd my_flask_app
pip install flask
python app.py

打开浏览器访问 http://127.0.0.1:5000,即可看到项目正常运行,整个过程无需手动搭建 Flask 项目结构,大幅提升开发效率。

五、Copier 与其他模板工具对比

与 Cookiecutter、Cookiecutter 等同类工具相比,Copier 具有明显优势:支持项目增量更新,无需重新生成项目;配置文件采用 YAML 格式,比 JSON 更简洁;兼容 Jinja2 语法,学习成本低;支持条件生成、动态目录名、变量过滤等高级功能,适配更复杂的项目场景。同时,Copier 轻量化,安装速度快,不依赖多余第三方库,适合个人开发者、中小团队快速标准化项目。

六、Copier 常见问题与解决方案

  1. 执行 copier 命令提示未找到
    解决方案:检查 Python 的 Scripts 目录是否添加到系统环境变量,重新安装后重启命令行。
  2. 模板变量未正确渲染
    解决方案:确认文件后缀为 .jinja2,变量语法为 {{ 变量名 }},检查 copier.yml 变量名称是否一致。
  3. 远程模板拉取失败
    解决方案:检查网络连接,确认 Git 工具正常,可使用 HTTPS 地址替代 SSH 地址拉取模板。
  4. copier update 出现文件冲突
    解决方案:根据命令行提示,选择保留本地文件或覆盖为模板文件,手动合并关键代码。

相关资源

  • Pypi地址:https://pypi.org/project/copier
  • Github地址:https://github.com/copier-org/copier
  • 官方文档地址:https://copier.readthedocs.io/

关注我,每天分享一个实用的Python自动化工具。

Python 一键绘制架构图:diagrams 库从入门到实战教程

一、diagrams 库概述

diagrams 是一款基于 Python 的架构图绘制库,无需借助 Visio、ProcessOn、Draw.io 等图形工具,仅通过代码即可快速生成云服务、微服务、Web 服务等架构图。其底层依赖 Graphviz 渲染图像,将代码逻辑转化为结构化图形,支持 AWS、Azure、阿里云、K8s 等主流云平台图标。

该库采用 MIT 开源许可证,可自由商用、修改与分发。优点是代码化绘图、版本可控、集成开发流程,适合程序员快速输出架构图;缺点是复杂自定义图形能力较弱,依赖 Graphviz 环境,过度复杂的流程图绘制效率较低。

二、环境准备与安装

2.1 依赖工具安装

diagrams 本身是 Python 库,但图像渲染依赖 Graphviz,必须先安装该工具,否则代码无法正常运行。

  • Windows:前往 Graphviz 官网下载安装包,安装后配置系统环境变量
  • macOS:使用 Homebrew 安装
brew install graphviz
  • Linux(Ubuntu/Debian)
sudo apt-get install graphviz

2.2 diagrams 库安装

安装完 Graphviz 后,直接使用 pip 安装 diagrams 库:

pip install diagrams

安装完成后,可通过以下代码验证环境是否正常:

# 环境验证代码
from diagrams import Diagram

# 若执行无报错,说明环境配置成功
print("diagrams 环境就绪")

执行后无异常输出,即代表安装与依赖均正常,可进入正式使用。

三、diagrams 基础使用语法

3.1 核心组件介绍

diagrams 绘图的核心由三部分组成:

  1. Diagram:画布,定义图表名称、方向、输出格式等
  2. Node:节点,代表架构中的组件(服务器、数据库、客户端等)
  3. Cluster:集群,用于对节点进行分组,形成逻辑区域

3.2 基础绘图流程

  1. 导入 Diagram 与对应资源模块
  2. 创建 Diagram 对象,设置图表信息
  3. 定义节点,使用 >> 符号建立节点间调用关系
  4. 运行代码,自动生成 PNG/SVG 格式图片

3.3 最简单的入门示例

# 导入基础依赖
from diagrams import Diagram
from diagrams.aws.compute import EC2
from diagrams.aws.database import RDS
from diagrams.aws.network import ELB

# 定义图表:名称、方向、输出格式
with Diagram("Web 服务架构", show=False, direction="LR"):
    # 定义节点与调用关系
    ELB("负载均衡") >> EC2("Web 服务器") >> RDS("数据库")

代码说明:

  • with Diagram(...) 创建绘图上下文,自动管理资源
  • direction="LR" 表示从左到右绘图,也可使用 TB(从上到下)
  • show=False 表示运行后不自动打开图片,仅保存
  • 节点使用对应云服务图标,名称可自定义

执行代码后,会在当前目录生成名为 web_服务架构.png 的架构图,直观展示负载均衡、Web 服务器、数据库的调用流程。

四、常用节点类型与分组使用

4.1 主流云平台节点

diagrams 内置大量云厂商图标,满足不同场景需求:

  • AWS:EC2、RDS、S3、Lambda、ELB 等
  • Azure:虚拟机、SQL 数据库、存储等
  • 阿里云:ECS、RDS、OSS、SLB 等
  • Kubernetes:Pod、Service、Ingress、Node 等
  • 通用组件:Client、Server、DB、Nginx、Docker 等

4.2 集群(Cluster)分组用法

当架构中存在多个同类组件时,可使用 Cluster 进行分组,让图表结构更清晰。

from diagrams import Diagram, Cluster
from diagrams.aws.compute import EC2
from diagrams.aws.database import RDS, ElastiCache
from diagrams.aws.network import ELB
from diagrams.aws.storage import S3

with Diagram("复杂 Web 架构", show=False, direction="LR"):
    # 定义客户端
    client = EC2("用户客户端")

    # 前端服务集群
    with Cluster("前端服务组"):
        lb = ELB("负载均衡")
        servers = [
            EC2("Web 1"),
            EC2("Web 2"),
            EC2("Web 3")
        ]

    # 数据服务集群
    with Cluster("数据层"):
        db = RDS("主库")
        cache = ElastiCache("Redis 缓存")
        storage = S3("对象存储")

    # 建立调用关系
    client >> lb >> servers
    servers >> db
    servers >> cache
    servers >> storage

代码说明:

  • Cluster 可将多个节点包裹,形成独立视觉区域
  • 列表形式可批量定义同类节点,简化代码
  • 支持一对多、多对一的连接关系,自动生成连线

该示例展示了带集群分组的完整 Web 架构,适合用于项目文档、技术分享场景。

五、微服务与中间件架构实战

5.1 微服务基础架构示例

from diagrams import Diagram, Cluster
from diagrams.onprem.client import User
from diagrams.onprem.compute import Server
from diagrams.onprem.database import PostgreSQL
from diagrams.onprem.inmemory import Redis
from diagrams.onprem.network import Nginx
from diagrams.onprem.queue import RabbitMQ
from diagrams.onprem.logging import ELK

with Diagram("微服务架构", show=False, direction="TB"):
    user = User("用户")

    with Cluster("网关层"):
        gateway = Nginx("网关服务")

    with Cluster("业务微服务"):
        user_service = Server("用户服务")
        order_service = Server("订单服务")
        pay_service = Server("支付服务")

    with Cluster("中间件"):
        mq = RabbitMQ("消息队列")
        cache = Redis("缓存")
        db = PostgreSQL("数据库")

    with Cluster("日志监控"):
        elk = ELK("日志系统")

    user >> gateway
    gateway >> [user_service, order_service, pay_service]
    [user_service, order_service, pay_service] >> mq
    [user_service, order_service, pay_service] >> cache
    [user_service, order_service, pay_service] >> db
    [user_service, order_service, pay_service] >> elk

代码说明:

  • 使用 onprem 系列节点,适用于自建机房、私有部署架构
  • 清晰划分网关层、业务服务、中间件、监控系统
  • 多服务统一调用中间件,结构一目了然

该图表可直接用于毕业设计、项目方案书、技术架构评审等场景。

5.2 前后端分离项目架构

from diagrams import Diagram
from diagrams.onprem.client import User
from diagrams.onprem.compute import Server
from diagrams.onprem.container import Docker
from diagrams.onprem.database import MongoDB
from diagrams.onprem.network import Nginx
from diagrams.programming.language import Python, Javascript
from diagrams.devops import Git, Jenkins

with Diagram("前后端分离架构", show=False):
    user = User("用户")

    frontend = Javascript("前端 Vue/React")
    web = Nginx("Web 服务")
    backend = Python("后端服务")
    db = MongoDB("数据库")

    with Cluster("DevOps 流程"):
        git = Git("代码仓库")
        ci = Jenkins("CI/CD")
        docker = Docker("容器部署")

    user >> frontend >> web >> backend >> db
    git >> ci >> docker >> backend

代码说明:

  • 支持编程语言、DevOps 工具、容器等通用节点
  • 完整展示从用户访问到开发部署的全流程
  • 适合前后端分离项目、前端开发、全栈开发场景

六、DevOps 与容器云架构

6.1 Docker + Kubernetes 架构

from diagrams import Diagram, Cluster
from diagrams.onprem.client import User
from diagrams.onprem.container import Docker
from diagrams.k8s.compute import Pod, Deployment
from diagrams.k8s.network import Ingress, Service
from diagrams.k8s.cluster import Node
from diagrams.onprem.monitoring import Prometheus, Grafana

with Diagram("K8s 容器架构", show=False, direction="LR"):
    user = User("用户")

    with Cluster("K8s 集群"):
        ingress = Ingress("入口")
        svc = Service("服务")
        deploy = Deployment("部署")
        pods = [
            Pod("Pod 1"),
            Pod("Pod 2")
        ]
        node = Node("工作节点")

    with Cluster("监控系统"):
        prom = Prometheus("监控采集")
        graf = Grafana("可视化")

    docker = Docker("容器")

    user >> ingress >> svc >> deploy >> pods >> node
    pods >> docker
    pods >> prom
    prom >> graf

代码说明:

  • 专门提供 Kubernetes 全套图标,适配云原生场景
  • 展示 Ingress、Service、Deployment、Pod 层级关系
  • 适合运维、云原生、后端开发人员使用

七、多场景综合架构案例

7.1 电商平台极简架构

from diagrams import Diagram, Cluster
from diagrams.onprem.client import User
from diagrams.onprem.network import Nginx
from diagrams.onprem.compute import Server
from diagrams.onprem.database import MySQL
from diagrams.onprem.queue import RabbitMQ
from diagrams.onprem.inmemory import Redis
from diagrams.aws.storage import S3
from diagrams.onprem.security import Vault

with Diagram("电商平台架构", show=False):
    users = User("用户")

    with Cluster("接入层"):
        lb = Nginx("负载均衡")

    with Cluster("业务服务"):
        goods = Server("商品服务")
        order = Server("订单服务")
        pay = Server("支付服务")
        user_srv = Server("用户服务")

    with Cluster("数据与中间件"):
        db = MySQL("业务库")
        redis = Redis("缓存")
        mq = RabbitMQ("消息队列")
        oss = S3("图片存储")
        secret = Vault("密钥管理")

    users >> lb >> [goods, order, pay, user_srv]
    [goods, order, pay, user_srv] >> [db, redis, mq, oss, secret]

代码说明:

  • 覆盖电商核心服务:用户、商品、订单、支付
  • 包含数据库、缓存、消息队列、对象存储、密钥服务
  • 结构简洁,可直接用于项目介绍与架构说明

7.2 个人项目/博客系统架构

from diagrams import Diagram
from diagrams.onprem.client import User
from diagrams.onprem.network import Nginx
from diagrams.programming.language import Python
from diagrams.onprem.database import SQLite
from diagrams.onprem.hosting import Server
from diagrams.devops import Vercel

with Diagram("个人博客架构", show=False):
    user = User("访客")
    nginx = Nginx("Web 服务")
    backend = Python("Django/Flask")
    db = SQLite("轻量数据库")
    deploy = Vercel("云部署")
    server = Server("轻量服务器")

    user >> nginx >> backend >> db
    deploy >> server
    server >> backend

代码说明:

  • 适合个人博客、小型工具、毕业设计项目
  • 使用轻量组件,部署简单,成本低
  • 图表清晰,适合写在项目 README 中

八、高级用法:样式与输出控制

8.1 图表样式配置

from diagrams import Diagram
from diagrams.aws.compute import EC2

with Diagram(
    "自定义样式",
    show=False,
    direction="LR",
    filename="custom_style",
    outformat="svg",
    graph_attr={
        "fontsize": "16",
        "bgcolor": "white",
        "labeljust": "center"
    },
    node_attr={
        "fontsize": "12",
        "shape": "box",
        "style": "filled"
    }
):
    EC2("自定义节点样式")

代码说明:

  • filename 指定输出文件名
  • outformat 支持 png、svg、pdf 等格式
  • graph_attrnode_attr 可自定义背景、字体、形状、颜色

8.2 批量节点简化写法

from diagrams import Diagram
from diagrams.aws.compute import EC2

with Diagram("批量节点", show=False):
    # 列表快速生成多个节点
    web_servers = [EC2(f"Web{i}") for i in range(1, 4)]
    [EC2("入口")] >> web_servers

代码说明:

  • 使用列表推导式快速生成多个同类节点
  • 适合大规模服务架构,减少重复代码

九、相关资源

  • Pypi地址:https://pypi.org/project/diagrams/
  • Github地址:https://github.com/mingrammer/diagrams
  • 官方文档地址:https://diagrams.mingrammer.com/

关注我,每天分享一个实用的Python自动化工具。