公司动态

用Python实现健身重量进度计算器:1RM估算与训练容量分析

📅 2026/8/29 13:19:46
用Python实现健身重量进度计算器:1RM估算与训练容量分析
最近去健身房训练时越来越觉得“小本本 Excel”记训练数据的方式有点吃力了。重量递进、次数波动、同一个动作到底该怎么加片全靠记忆和翻历史记录时间一长根本对不上。于是抽空自己动手做了一个 gym weight progress calculator也就是健身重量进度计算器。本文把这套方案的完整设计、核心算法、代码实现和踩坑思路整理出来代码是基于 Python 标准库写的不需要安装第三方依赖可以直接复制运行。如果你也遇到类似的问题比如记录了很多次训练但不知道每个动作的 1RM一次最大重量到底是多少想判断这次训练是否比上次“进步了”却不知道怎么量化想用代码管理训练日志但不想一上来就引入数据库或 Web 框架那这篇文章很适合你。下面我们从概念开始逐步实现一个完整可用的训练进度计算器。1. 背景与核心概念1.1 为什么要做重量进度计算器力量训练里有一个非常重要原则叫“渐进超负荷”Progressive Overload。简单说就是随着训练水平提高你需要逐步增加训练重量、次数或组数才能持续刺激肌肉生长和力量增长。但问题是训练计划不是一成不变的每天状态也不同。如果只凭感觉判断“上次练多重这次练多重”经常会出现两种情况重量加得太快导致动作变形、受伤风险增加重量长期不变化训练效果进入平台期。所以最可靠的方式就是每次训练后记录数据然后定期对比。比如这次卧推 60kg 做了 8 次下次要不要尝试 62.5kg如果换了 5×5 计划又该怎么跟 4×8 计划比较这时就需要一个工具把所有记录变成可比较的量化指标。常用来比较的指标有两个1RM 估算值把任意次数下的重量转换成“理论最大重量”训练容量重量 × 次数 × 组数反映总训练量。重量进度计算器的主要职责就是自动完成这两个计算并帮助你观察长期变化趋势。1.2 容易混淆的概念在继续之前先分清几个概念重量Weight杠铃或器械上实际加载的重量单位一般为 kg 或 lb。次数Reps一组动作中完成的次数。组数Sets完成多少组。1RMOne Repetition Maximum一个动作只能完成 1 次的最大重量。训练容量Volume重量 × 次数 × 组数是衡量训练总量的常用指标。它们之间的区别非常重要。例如一个人用 100kg 卧推做 5 次另一个人用 80kg 卧推做 10 次两人使用的绝对重量不同但训练容量却很接近。如果只看重量容易误判训练强度如果只看容量又可能忽略了重量本身的刺激强度。这也是为什么计算器要同时展示 1RM 和容量让对比维度更完整。2. 需求分析与功能设计2.1 核心功能在动手写代码之前先明确这个工具要解决什么问题。我把最核心的功能拆成了四个添加训练记录输入动作名称、日期、重量、次数、组数自动计算 1RM根据选定公式估算当前动作的理论最大重量查看历史记录按动作筛选展示该动作所有训练记录及 1RM 变化生成训练汇总汇总所有动作的最新 1RM、历史最佳、训练次数等。额外可以考虑的功能包括RPE主观疲劳度记录、训练容量趋势、导出 CSV、可视化图表等。但第一版不建议做太复杂先把核心链路跑通。2.2 用户场景这个工具适合以下几类场景个人训练记录每天训练后输入一条记录定期查看趋势训练计划对比在不同计划切换时用 1RM 和容量判断力量是否真的进步简单数据分析想用程序处理数据但不想为了一个脚本引入数据库和前端。本项目采用“命令行工具 JSON 文件存储”的方案原因是数据量不大、单机使用、方便备份和修改。后续如果需要多人协作或可视化再迁移到 Web 应用也不迟。2.3 技术选型我选择 Python 3 作为实现语言理由很简单Python 标准库自带json、argparse、dataclasses不需要安装额外依赖类型提示和 dataclass 让数据结构更清晰跨平台Windows、macOS、Linux 都能运行。数据存储使用 JSON 文件而不是 SQLite主要是因为训练记录结构简单、字段固定用 JSON 可读性好也方便手工编辑和备份。3. 环境准备与项目结构3.1 环境要求本文示例以 Python 3.10 环境为例重点演示实现思路。如果你的环境是 Python 3.8 或 3.9大部分代码也能运行只是部分类型写法可能需要微调。无需安装第三方库只需要系统自带的标准库。确认 Python 版本python3 --version如果输出类似Python 3.10.12说明环境没问题。3.2 项目结构项目很简单只有一个主文件和一个运行后生成的数据文件。gym_progress/ ├── gym_progress.py # 主程序 └── training_data.json # 运行后自动生成的数据文件gym_progress.py是核心入口里面包含数据模型、计算逻辑和命令行交互。training_data.json由程序自动创建和维护。4. 核心算法与数据模型4.1 1RM 估算公式1RM 直接测试很危险一般通过多次数训练记录来估算。常见公式有Epley 公式1RM weight × (1 reps / 30)Brzycki 公式1RM weight × (36 / (37 - reps))Lombardi 公式1RM weight × reps^0.10三个公式各有特点。Epley 公式使用最广泛适合 1~10 次范围内的估算Brzycki 公式在中等次数6~12 次下表现稳定Lombardi 公式倾向给高次数记录更高的估算值。本工具默认使用 Epley 公式同时在命令行中允许切换公式。注意无论哪种公式估算值都不等于实际 1RM。大重量测试需要专业保护估算结果只用于训练计划参考。4.2 训练容量训练容量计算公式容量 重量 × 次数 × 组数例如60kg 卧推做 8 次、共 4 组容量 60 × 8 × 4 1920比较两次训练时容量变化能直观反映训练量是否提升。但容量上升不一定等于变强还需要结合 1RM 趋势一起判断。4.3 数据结构设计每条训练记录包含以下字段字段类型说明exercisestr动作名称例如 卧推、深蹲weightfloat使用的重量repsint每组次数setsint组数datestr训练日期格式 YYYY-MM-DDrpefloat可选主观疲劳度 1-10JSON 文件存成数组便于追加和读取。示例[ { exercise: 卧推, weight: 60.0, reps: 8, sets: 4, date: 2024-01-15, rpe: 8.0 } ]5. 完整代码实现5.1 创建项目目录在终端执行mkdir gym_progress cd gym_progress然后创建gym_progress.py文件。5.2 完整代码下面是gym_progress.py的完整实现可以直接复制保存#!/usr/bin/env python3 # -*- coding: utf-8 -*- gym_progress.py - 健身重量进度计算器 功能 1. 添加训练记录 2. 根据记录估算 1RM 3. 查看指定动作的历史记录 4. 汇总所有动作的训练情况 用法示例 python3 gym_progress.py add --exercise 卧推 --weight 60 --reps 8 --sets 4 python3 gym_progress.py history --exercise 卧推 python3 gym_progress.py summary import argparse import json import os import sys from dataclasses import dataclass, asdict from datetime import date from typing import List, Optional DATA_FILE training_data.json dataclass class TrainingRecord: exercise: str weight: float reps: int sets: int date: str rpe: Optional[float] None property def volume(self) - float: 训练容量 重量 × 次数 × 组数 return self.weight * self.reps * self.sets def load_records(file_path: str DATA_FILE) - List[dict]: 从 JSON 文件加载训练记录。 if not os.path.exists(file_path): return [] try: with open(file_path, r, encodingutf-8) as f: data json.load(f) if isinstance(data, list): return data return [] except json.JSONDecodeError as e: print(f数据文件解析失败请检查 {file_path} 的 JSON 格式{e}) sys.exit(1) def save_records(records: List[dict], file_path: str DATA_FILE) - None: 把训练记录写入 JSON 文件使用临时文件替换避免写入中断导致文件损坏。 tmp_file file_path .tmp with open(tmp_file, w, encodingutf-8) as f: json.dump(records, f, ensure_asciiFalse, indent2) os.replace(tmp_file, file_path) def calc_1rm(weight: float, reps: int, formula: str epley) - float: 根据重量和次数估算 1RM。 公式说明 - epley: 1RM weight * (1 reps / 30) - brzycki: 1RM weight * (36 / (37 - reps)) - lombardi: 1RM weight * (reps ** 0.10) if weight 0: raise ValueError(重量必须大于 0) if reps 0: raise ValueError(次数必须大于 0) if reps 1: return weight if formula epley: return weight * (1 reps / 30) elif formula brzycki: if reps 37: raise ValueError(Brzycki 公式不适用于 37 次及以上的记录) return weight * (36 / (37 - reps)) elif formula lombardi: return weight * (reps ** 0.10) else: raise ValueError(f未知公式{formula}可选值epley / brzycki / lombardi) def add_record(record: dict) - None: 添加一条训练记录。 records load_records() records.append(record) save_records(records) def get_records_by_exercise(records: List[dict], exercise: str) - List[dict]: 按动作名称筛选训练记录并按日期排序。 matched [r for r in records if r[exercise] exercise] matched.sort(keylambda r: r[date]) return matched def format_history(records: List[dict], formula: str epley) - str: 格式化某个动作的历史记录。 if not records: return 暂无训练记录。 # 中文可能造成对齐问题这里使用基本格式 lines [] lines.append(f{日期:12} {重量:8} {次数:6} {组数:6} {1RM:10} {容量:8}) lines.append(- * 60) for r in records: one_rm calc_1rm(r[weight], r[reps], formula) volume r[weight] * r[reps] * r[sets] lines.append( f{r[date]:12} {r[weight]:8} {r[reps]:6} f{r[sets]:6} {one_rm:10.2f} {volume:8.0f} ) return \n.join(lines) def generate_summary(formula: str epley) - str: 汇总所有动作的最新 1RM、次数和最新日期。 records load_records() if not records: return 暂无训练数据。 exercises sorted(set(r[exercise] for r in records)) lines [] lines.append(f{动作:12} {最新1RM:10} {历史最佳1RM:14} {训练次数:8} {最近日期:12}) lines.append(- * 60) for ex in exercises: ex_records get_records_by_exercise(records, ex) latest ex_records[-1] latest_1rm calc_1rm(latest[weight], latest[reps], formula) best_1rm max(calc_1rm(r[weight], r[reps], formula) for r in ex_records) lines.append( f{ex:12} {latest_1rm:10.2f} {best_1rm:14.2f} f{len(ex_records):8} {latest[date]:12} ) return \n.join(lines) def cmd_add(args) - None: 处理 add 子命令。 record { exercise: args.exercise, weight: args.weight, reps: args.reps, sets: args.sets, date: args.date, rpe: args.rpe, } add_record(record) one_rm calc_1rm(args.weight, args.reps, args.formula) print(f已添加记录{args.exercise} {args.weight}kg × {args.reps} 次 × {args.sets} 组) print(f估算 1RM{one_rm:.2f}kg{args.formula} 公式) def cmd_history(args) - None: 处理 history 子命令。 records load_records() ex_records get_records_by_exercise(records, args.exercise) print(format_history(ex_records, args.formula)) def cmd_summary(args) - None: 处理 summary 子命令。 print(generate_summary(args.formula)) def main(): parser argparse.ArgumentParser( descriptionGym Weight Progress Calculator - 健身重量进度计算器 ) parser.add_argument( --formula, defaultepley, choices[epley, brzycki, lombardi], help1RM 估算公式默认 epley, ) subparsers parser.add_subparsers(destcommand, requiredTrue) # add 子命令 add_parser subparsers.add_parser(add, help添加一条训练记录) add_parser.add_argument(--exercise, requiredTrue, help动作名称) add_parser.add_argument(--weight, typefloat, requiredTrue, help重量) add_parser.add_argument(--reps, typeint, requiredTrue, help次数) add_parser.add_argument(--sets, typeint, default1, help组数) add_parser.add_argument(--date, defaultdate.today().isoformat(), help日期默认今天) add_parser.add_argument(--rpe, typefloat, defaultNone, helpRPE 主观疲劳度 1-10) # history 子命令 history_parser subparsers.add_parser(history, help查看指定动作的历史记录) history_parser.add_argument(--exercise, requiredTrue, help动作名称) # summary 子命令 subparsers.add_parser(summary, help查看所有动作的汇总) args parser.parse_args() if args.command add: cmd_add(args) elif args.command history: cmd_history(args) elif args.command summary: cmd_summary(args) if __name__ __main__: main()5.3 代码拆解这个文件虽然不长但包含了几个常见工程实践dataclass定义数据结构让记录字段一目了然load_records/save_records封装文件读写逻辑主流程不关心 JSON 细节save_records使用临时文件 os.replace保证写入过程中程序被中断也不会留下半个 JSON 文件calc_1rm集中处理公式计算并在参数不合法时抛出异常cmd_*函数负责命令行输出把业务逻辑和展示逻辑分离。这里的关键点是“单一职责”。不要让calc_1rm同时负责命令行输出也不要在main里直接操作文件。这样后续增加新命令、换存储方式时改动范围会更小。6. 运行与验证6.1 添加训练记录在项目目录执行python3 gym_progress.py add --exercise 卧推 --weight 60 --reps 8 --sets 4预期输出已添加记录卧推 60.0kg × 8 次 × 4 组 估算 1RM76.00kgepley 公式再添加一条数据python3 gym_progress.py add --exercise 卧推 --weight 62.5 --reps 6 --sets 4 python3 gym_progress.py add --exercise 深蹲 --weight 80 --reps 5 --sets 5此时training_data.json文件应该自动生成并且包含三条记录。6.2 查看动作历史python3 gym_progress.py history --exercise 卧推预期输出日期 重量 次数 组数 1RM 容量 ------------------------------------------------------------ 2025-01-15 60.0 8 4 76.00 1920 2025-01-22 62.5 6 4 75.00 1500从数据可以看到第一次训练重量低但次数高第二次重量提升但次数下降两种情况下 1RM 非常接近。这正好说明如果只看重量或只看次数很难判断进步但 1RM 提供了一个相对统一的比较尺度。6.3 查看汇总python3 gym_progress.py summary预期输出动作 最新1RM 历史最佳1RM 训练次数 最近日期 ------------------------------------------------------------ 卧推 75.00 76.00 2 2025-01-22 深蹲 90.67 90.67 1 2025-01-22汇总页面能一眼看到每个动作的最新状态和历史最佳。6.4 切换 1RM 公式如果你想用 Brzycki 公式比较历史记录python3 gym_progress.py history --exercise 卧推 --formula brzycki不同公式会给出不同估算值建议长期使用同一个公式避免数据不一致。7. 常见问题与排查思路在实际使用过程中可能会遇到以下问题问题现象常见原因解决思路运行报ModuleNotFoundError环境不是 Python 3或解释器设置错误确认使用python3命令而不是python添加记录时报重量必须大于 0输入的 weight 为负数或 0检查参数重量必须为正数历史记录中中文对齐错乱终端字体和中文宽度不一致不影响数据可忽略或改用表格库打开数据文件时报 JSONDecodeError手工编辑 JSON 时少写了逗号或花括号用python3 -m json.tool training_data.json检查格式想删除一条错误记录程序没有提供删除命令备份后手工编辑 JSON 文件或用文本编辑器删除对应数组元素1RM 结果明显偏高使用了高次数记录估算 1RM1RM 估算公式最适合 1~12 次范围高次数估算仅供参考同一动作名称大小写不同中文名称中混入了空格或不同写法添加记录时保持动作名称一致或者在程序里做名称归一化7.1 数据格式检查方法如果 JSON 文件损坏可以先备份再执行python3 -m json.tool training_data.json如果输出格式化的 JSON说明文件没有问题如果报错会提示具体行号和原因。7.2 恢复误删数据没有内置恢复功能建议定期备份training_data.json。最简单的备份方法是复制一份带日期的文件cp training_data.json training_data_20250122.json8. 进阶扩展方向第一版跑通后可以按需求逐步扩展。8.1 数据可视化可以用matplotlib绘制每个动作的 1RM 趋势曲线直观看到进步幅度。核心思路是先按日期排序再画出折线图。这里只给出思路因为画图需要安装第三方库且不同环境字体配置不同。8.2 支持更多指标当前只有重量、次数、组数后续可以增加RPE记录主观疲劳度帮助判断强度是否过高动作类型区分推、拉、腿等部位休息时间组间休息影响训练表现身体体重辅助判断相对力量变化。这些字段都可以在TrainingRecord中扩展。8.3 Web 界面如果命令行不够直观可以考虑用 Flask 或 FastAPI 做一个简单 Web 界面把 JSON 文件作为后端存储前端提供表单录入和展示页面。需要注意Web 项目涉及依赖管理和部署复杂度会比命令行版本高不少。8.4 自动提醒可以加入“建议重量”功能当连续两次训练 1RM 稳定后自动给出下次推荐重量推荐重量 当前 1RM × 85% 或 90%但要注意自动推荐只是参考实际训练中还是要根据自己的状态和动作标准来调整。9. 最佳实践与工程建议9.1 数据安全与备份训练数据是长期积累的结果一旦丢失非常可惜。建议把training_data.json纳入备份策略例如每周复制一次到网盘或移动硬盘使用 Git 管理数据文件每次修改后提交重要阶段手动导出 CSV。9.2 代码可维护性虽然是一个小工具也建议把“计算”和“展示”分开。以后如果要加数据库、加图表、加多用户只需要替换数据层核心算法不用动。9.3 参数校验在生产环境或长期使用中不能信任输入数据。添加记录前应对关键参数做校验weight 必须大于 0reps 必须在 1 到 100 之间sets 必须大于 0date 格式必须是 YYYY-MM-DD。当前代码对 weight 和 reps 做了基本校验但日期格式没有校验可以自行完善。9.4 单元测试calc_1rm是纯函数非常适合写单元测试。例如assert calc_1rm(60, 1) 60 assert calc_1rm(60, 10, epley) 80把这类断言放到测试文件中后续修改公式时能快速发现回归问题。9.5 与训练计划的结合这个工具不是代替训练计划而是帮你量化计划执行效果。建议每月回顾一次汇总数据重点观察每个动作的 1RM 是否在上升训练容量是否长期停滞是否有动作长期没有进步需要调整动作变式或计划安排。记录数据只是第一步真正有价值的是定期复盘。10. 总结这个健身重量进度计算器核心并不复杂但把训练中最容易模糊的两个指标——1RM 和训练容量——用代码量化了。做完之后最直接的变化是每次训练完只需花十几秒输入一条记录后续看趋势、决定是否加重都有了数据依据。从代码角度看这个项目麻雀虽小五脏俱全有数据结构、文件读写、命令行解析、算法计算、异常提示适合作为 Python 入门后的练手项目。如果你想继续深入可以尝试接入 SQLite 存储、用 matplotlib 画趋势图或者做成 Flask 网页应用。如果你也在记录自己的训练数据不妨复制代码试运行一遍再根据自己的训练习惯调整字段和公式。数据记录本身不产生进步但数据指导下一次训练选择才是这个计算器真正的价值。