2026/10/13 15:16:12

DeepSeek Harness插件开发全指南:从命令行胶水到上下文感知Skill

DeepSeek Harness插件开发全指南:从命令行胶水到上下文感知Skill 1. DeepSeek Harness 是什么它和你日常用的 IDE 工具链到底是什么关系很多人第一次看到“DeepSeek Harness”这个词第一反应是这又是个新出的 AI 编程助手是不是类似 Copilot 或者 CodeWhisperer 的桌面版其实不是。DeepSeek Harness常缩写为dsh本质上是一个命令行原生、可插件化扩展的本地开发协作者框架不是独立 IDE也不是云端服务更不是浏览器插件——它是一套运行在你本机终端里的、面向开发者工作流的“智能胶水层”。我第一次接触 dsh 是在调试一个跨平台 Python CLI 工具时。当时需要频繁切换环境变量、重载配置、验证 JSON Schema、比对 Git diff 输出手动敲命令太碎、太重复。试过 shell alias、Makefile、甚至写了个小脚本但都卡在“状态感知弱”“上下文不连贯”“无法复用已有工具逻辑”这三点上。直到同事甩给我一行命令dsh run --skillgit-diff-analyze我才发现——原来不是我在调工具而是工具在理解我的意图。dsh 的核心定位非常清晰它不替代 VS Code、IntelliJ 或 Vim而是让这些工具背后的真实操作shell 命令、API 调用、文件解析、Git 操作、日志提取变得可语义化、可组合、可记忆、可复用。它的“Harness”一词直译就是“挽具”或“束带”形象地说明了它的作用——把散落在终端、配置文件、脚本、文档里的开发动作用统一的技能Skill接口“捆扎”起来再通过插件Plugin注入新能力。这和你熟悉的 IDEA 插件开发、Chrome 插件开发有本质区别IDEA 插件扩展的是 GUI 界面行为比如右键菜单、编辑器高亮、项目视图节点底层依赖 IntelliJ Platform SDKChrome 插件扩展的是网页 DOM 和浏览器 API受限于 Content Security Policy 和沙箱机制而dsh 插件扩展的是命令行会话中的“语义动作单元”——它不画按钮不改页面只定义“当用户输入dsh commit --explain时该做什么当检测到当前目录含pyproject.toml且刚执行过git add .时是否自动触发dsh lint --auto-fix”。提示dsh 不是“AI 模型前端”它本身不包含大模型推理能力。所有 Skill 默认调用本地已安装的 CLI 工具如git,jq,yq,ripgrep,tree或通过curl/httpx调用你自建的内网 API。所谓“AI 增强”是通过插件把 LLM 的 prompt engineering 封装成可复用的 Skill比如dsh explain-code --filemain.py实际执行的是读取文件 → 构造结构化 prompt → 发送到你指定的http://localhost:8000/v1/chat/completions→ 解析响应 → 高亮输出关键段落。模型在哪里、用哪家完全由你控制。这也是为什么大量热词里反复出现 “dsh 插件下载”“dsh 插件市场”“dsh plugin --profile web add dshmarket”——因为 dsh 的价值不在开箱即用而在生态可塑性。它像 Linux 的/usr/bin目录本身只提供dsh这个可执行文件和基础 runtime真正的生产力来自你装进~/.dsh/plugins/里的那些.py或.sh文件。所以如果你是刚接触 dsh 的开发者别急着去搜“deepseek harness 下载”先问自己三个问题我每天在终端里重复敲哪些 3 行以上的命令组合哪些操作我总得查文档、复制粘贴、再手动修改参数哪些判断逻辑比如“当前分支名含 feat/ 且存在未提交变更”我本可以用代码自动化却一直靠脑子记这三个问题的答案就是你第一个 dsh 插件最该解决的场景。不是为了炫技而是为了把“人肉运维”变成“声明式执行”。2. 插件开发不是写 Hello World而是定义一套可被 dsh runtime 识别的契约很多新手教程一上来就贴代码“新建hello.py写def main(): print(Hello)然后dsh plugin install ./hello.py”。这就像教人做菜第一课让你切葱花却不告诉你葱花要配什么菜、火候怎么控、为什么必须斜刀切——结果就是插件装上了dsh hello也执行了但你完全不知道它怎么被调用、参数怎么传、错误怎么捕获、输出怎么被其他 Skill 消费。dsh 插件的本质是一组符合特定接口规范的 Python 模块目前仅支持 Python 3.9。它不是独立脚本而是 dsh runtime 的“插件实例”。要让 dsh 认出你的代码是插件必须满足四个硬性契约2.1 插件入口文件必须声明__dsh_plugin__ True这是 dsh 加载器识别插件的“身份证”。没有这行无论你文件名多么规范、函数多么标准dsh 都会直接跳过。它必须出现在模块顶层作用域不能在函数里、不能在 if 判断后、不能被注释掉。# ✅ 正确放在文件最顶部 __dsh_plugin__ True def main(args): print(Hello from dsh plugin!)# ❌ 错误条件赋值加载器无法静态分析 if True: __dsh_plugin__ True # dsh 不会执行这段代码来判断 # ❌ 错误被注释等于不存在 # __dsh_plugin__ True这个设计看似简单实则关键。dsh 在启动时采用静态导入扫描而非动态 import exec即遍历~/.dsh/plugins/下所有.py文件逐行读取源码用正则匹配^__dsh_plugin__\s*\s*True$。这样做的好处是零运行时开销、杜绝恶意插件动态注入、确保插件元信息绝对可信。坏处是你不能用from myutils import PLUGIN_FLAG这种方式间接设置——必须白纸黑字写死。2.2 必须提供main()函数且签名严格限定为def main(args: list[str]) - int注意这里args是原始命令行参数列表不是 argparse 解析后的 namespace。dsh 不帮你做参数解析它只负责把dsh myplugin --verbose -f config.yaml中的[--verbose, -f, config.yaml]原样传进来。解析工作交给你这是为了保持轻量和确定性。# ✅ 正确签名严格匹配返回 int 表示退出码 def main(args: list[str]) - int: import argparse parser argparse.ArgumentParser() parser.add_argument(--verbose, actionstore_true) parser.add_argument(-f, --file, requiredTrue) ns parser.parse_args(args) # ... 业务逻辑 return 0 # 成功为什么返回int因为 dsh 把插件当作子进程看待。main()返回0表示成功非0如1,2表示失败dsh 会据此决定是否继续执行后续 Skill 链。例如你在dsh run --chain中串联多个插件前一个返回非零后一个就不会触发。2.3 插件元数据必须通过__dsh_metadata__字典显式声明这是插件的“说明书”dsh 用它生成dsh plugin list的输出、做依赖检查、控制加载顺序。它必须是顶层变量类型为dict且至少包含name、version、description三项__dsh_plugin__ True __dsh_metadata__ { name: git-diff-analyze, version: 0.2.1, description: 自动解析 git diff 输出标出新增/删除行数、涉及文件类型、潜在风险关键词, author: your-name, homepage: https://github.com/you/dsh-git-diff, requires: [git, jq] # 声明依赖的 CLI 工具 } def main(args: list[str]) - int: # ...requires字段特别重要。dsh 在插件加载阶段会检查这些命令是否存在于$PATH中。如果git找不到dsh plugin list会显示该插件为INACTIVE (missing: git)且dsh git-diff-analyze执行时直接报错退出不会进入main()。这避免了运行时才发现环境缺失的尴尬。2.4 插件必须能被import且无副作用导入dsh 加载插件时会执行importlib.util.spec_from_file_location(plugin_name, plugin_path)然后module importlib.util.module_from_spec(spec)最后spec.loader.exec_module(module)。这意味着插件文件不能有if __name__ __main__:下的执行逻辑除非包裹在函数里不能在模块顶层os.system(rm -rf /)或requests.get(http://evil.com)所有耗时操作如网络请求、大文件读取必须放在main()里而不是模块导入时。我踩过一个典型坑为了方便调试在插件里写了print(Plugin loaded!)在顶层。结果dsh plugin list每次都打印这行而且是在扫描所有插件时——哪怕你只是想看个列表它也执行了。后来改成__dsh_plugin__ True # ✅ 把调试输出移到 main() 内 def main(args: list[str]) - int: print(Plugin loaded and executing!) # 只在真正调用时打印 # ...这就是契约思维dsh 不关心你内部怎么写只认这四条红线。越早建立这个认知越能避开后续 80% 的“插件装了但不生效”问题。3. 从零开始手把手实现一个真实可用的dsh log-tail插件光讲契约太干。我们来做一个真正解决痛点的插件dsh log-tail。它的需求很具体——在微服务开发中你经常要tail -f logs/app.log | grep ERROR但每次都要输一长串还要手动 CtrlC 停止更别说多服务日志聚合了。我们把它封装成dsh log-tail --serviceuser --levelERROR支持一键启动、按服务过滤、级别高亮、自动重启当 log 文件轮转时。3.1 项目结构与初始化首先创建插件目录结构。dsh 不强制要求但推荐以下布局便于后续维护和发布~/.dsh/plugins/log-tail/ ├── __init__.py # 空文件标识为 Python 包 ├── main.py # 插件主模块必须含 __dsh_plugin__ 和 main() ├── tailer.py # 核心逻辑模块可选解耦业务 └── README.md # 插件说明非必需但强烈建议初始化main.py# ~/.dsh/plugins/log-tail/main.py __dsh_plugin__ True __dsh_metadata__ { name: log-tail, version: 0.1.0, description: 实时监控服务日志支持按服务名、日志级别过滤和高亮, author: your-name, homepage: https://github.com/you/dsh-log-tail, requires: [tail, grep, awk] } import sys import os import subprocess import signal import time from pathlib import Path def main(args: list[str]) - int: # 后续填充 pass注意requires里列了tail,grep,awk——这三个是 Unix/Linux/macOS 基础工具Windows 用户需安装 WSL 或 Git Bash 才能用。这是 dsh 插件的现实约束它默认面向类 Unix 环境。如果你真要做 Windows 原生支持requires得换成Get-ContentPowerShell或typeCMD但跨平台兼容性会急剧下降通常建议统一用 WSL。3.2 参数解析与校验用argparse做健壮输入处理main()接收原始args我们要把它变成结构化数据。argparse是标准方案但要注意两点必须用parser.parse_args(args)不能用parser.parse_args()后者会从sys.argv读而sys.argv是dsh进程的参数不是你的插件参数对必填参数做存在性校验对路径参数做可访问性校验。def main(args: list[str]) - int: import argparse parser argparse.ArgumentParser( progdsh log-tail, description__dsh_metadata__[description] ) parser.add_argument( --service, -s, requiredTrue, help服务名称用于匹配日志文件名如 user, order, payment ) parser.add_argument( --level, -l, choices[DEBUG, INFO, WARN, ERROR, FATAL], defaultERROR, help日志级别过滤默认 ERROR ) parser.add_argument( --log-dir, -d, default./logs, help日志根目录默认 ./logs ) try: ns parser.parse_args(args) except SystemExit as e: # argparse 抛出 SystemExit(2) 表示参数错误dsh 期望我们返回 2 return 2 # 校验 log-dir 是否存在且可读 log_dir Path(ns.log_dir) if not log_dir.exists(): print(f❌ 日志目录不存在: {log_dir}) return 1 if not log_dir.is_dir(): print(f❌ 指定路径不是目录: {log_dir}) return 1 if not os.access(log_dir, os.R_OK): print(f❌ 日志目录不可读: {log_dir}) return 1 # 构建日志文件模式如 logs/user*.log log_pattern f{log_dir}/{ns.service}*.log # 后续 tail 逻辑...这里return 2是关键。argparse默认错误退出码是2dsh 会原样透传给 shell。用户执行dsh log-tail -s user --level INVALID时会看到标准的usage: ... error: argument --level: invalid choice体验和原生命令一致。3.3 核心逻辑用subprocess.Popen实现健壮的 tail grep 流式处理真正的难点不在参数而在如何让tail -f在 Python 里稳定运行、响应中断、处理文件轮转。subprocess.run()会阻塞不适合流式日志subprocess.Popen()是唯一选择但要小心信号处理和编码。def main(args: list[str]) - int: # ... 参数解析与校验同上 # 构建 tail 命令tail -f logs/user*.log | grep -E (ERROR|WARN) # 注意用 shellTrue 会引入安全风险但此处 pattern 来自 argparse已校验可控 tail_cmd ftail -f {log_pattern} grep_cmd fgrep -E ({ns.level}|{ns.level.lower()}) # 启动 tail 进程 tail_proc subprocess.Popen( tail_cmd, shellTrue, stdoutsubprocess.PIPE, stderrsubprocess.STDOUT, bufsize1, universal_newlinesTrue, encodingutf-8 ) # 启动 grep 进程stdin 接 tail 的 stdout grep_proc subprocess.Popen( grep_cmd, shellTrue, stdintail_proc.stdout, stdoutsubprocess.PIPE, stderrsubprocess.STDOUT, bufsize1, universal_newlinesTrue, encodingutf-8 ) # 关闭 tail 的 stdout避免僵尸进程 tail_proc.stdout.close() # 设置信号处理器捕获 CtrlC def signal_handler(signum, frame): print(\n 收到中断信号正在停止...) tail_proc.terminate() grep_proc.terminate() try: tail_proc.wait(timeout3) grep_proc.wait(timeout3) except subprocess.TimeoutExpired: tail_proc.kill() grep_proc.kill() sys.exit(0) signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) # 实时读取 grep 输出并高亮 try: for line in grep_proc.stdout: if ns.level ERROR: # ANSI 红色高亮 ERROR highlighted line.replace(ERROR, \033[91mERROR\033[0m) print(highlighted, end) elif ns.level WARN: highlighted line.replace(WARN, \033[93mWARN\033[0m) print(highlighted, end) else: print(line, end) except KeyboardInterrupt: # 这里其实不会触发因为 signal_handler 已接管 pass finally: # 确保进程清理 grep_proc.stdout.close() grep_proc.wait() tail_proc.wait() return 0这段代码有几个实战要点bufsize1和universal_newlinesTrue确保行缓冲每行都能即时输出而不是等满 4KBencodingutf-8显式指定编码避免 Windows 终端乱码tail_proc.stdout.close()这是关键如果不关grep_proc的stdin会一直挂着导致tail_proc无法正常退出双进程信号处理tail -f对SIGINT响应慢有时会残留所以signal_handler里要terminate()wait()kill()三连ANSI 颜色\033[91m是红色\033[0m是重置终端原生支持无需额外库。测试它dsh log-tail -s user -l ERROR然后在另一个终端echo ERROR: something wrong logs/user-api.log立刻就能看到红色高亮输出。这才是真实可用的生产力工具。3.4 插件安装与调试dsh plugin install的隐藏行为写完main.py别急着执行。先确认位置# 确保在插件目录下 cd ~/.dsh/plugins/log-tail/ # 安装注意路径必须是绝对路径或相对于当前目录的相对路径 dsh plugin install ./main.py # 或 dsh plugin install /home/you/.dsh/plugins/log-tail/main.pydsh plugin install干了三件事复制文件把main.py复制到~/.dsh/plugins/installed/log-tail-0.1.0/main.py版本号来自__dsh_metadata__生成符号链接在~/.dsh/plugins/active/下创建log-tail - ../installed/log-tail-0.1.0/触发重新加载dsh runtime 会扫描active/目录发现新链接立即导入模块并缓存__dsh_metadata__。注意dsh plugin install不会重新运行你的代码它只是文件管理。真正的main()执行只发生在dsh log-tail ...时。调试技巧加print()调试在main()开头加print(f[DEBUG] args: {args})执行dsh log-tail -s test看输出查看插件状态dsh plugin list会显示log-tail 0.1.0 ACTIVE如果显示INACTIVE检查requires里的命令是否存在手动导入测试在 Python REPL 里import sys; sys.path.append(/home/you/.dsh/plugins/log-tail); import main; main.main([-s, test])绕过 dsh 加载器快速验证逻辑。这个log-tail插件虽小但涵盖了插件开发的核心环节契约遵守、参数健壮、进程管理、信号处理、编码适配。它不是一个玩具而是你能立刻放进 daily workflow 的真实工具。4. 插件进阶如何让dsh理解你的项目上下文并自动触发 Skill上面的log-tail是被动式插件——你得主动敲命令。但 dsh 的终极魅力在于上下文感知的主动式 Skill。比如当你cd进一个 Go 项目目录dsh 能自动提示“检测到 go.mod是否运行dsh go-deps-check”当你git commit后它能自动执行dsh lint --staged。这靠的是 dsh 的Context Hook 机制。4.1 Context Hook 是什么它不是监听器而是“快照比对器”很多新手以为 Context Hook 是类似inotifywait的文件系统监听其实不然。dsh 的 Context Hook 是基于 Shell Prompt 的轻量快照。它在每次你按下回车执行命令前采集当前环境的几个关键维度当前工作目录PWD当前 Git 仓库状态git rev-parse --abbrev-ref HEAD 2/dev/null当前目录下的特征文件如go.mod,package.json,pyproject.toml,.dsh-context环境变量可配置如CI,ENV然后它把这些快照和你插件里定义的context_rules进行匹配。匹配成功就触发预设的 Skill。举个例子你想在进入任何含pyproject.toml的目录时自动运行dsh python-env-check。你需要在插件里定义# ~/.dsh/plugins/python-env/main.py __dsh_plugin__ True __dsh_metadata__ { name: python-env, version: 0.1.0, description: Python 项目环境检查插件, author: your-name, requires: [python, pip] } # 新增context_rules一个列表每个元素是 dict __dsh_context_rules__ [ { name: pyproject-detected, trigger: dir_contains, pattern: pyproject.toml, action: run_skill, skill: python-env-check, message: ✅ 检测到 Python 项目运行环境检查... } ] def main(args: list[str]) - int: if len(args) 0 and args[0] python-env-check: # 实际检查逻辑 import subprocess try: subprocess.run([python, --version], checkTrue, capture_outputTrue) subprocess.run([pip, --version], checkTrue, capture_outputTrue) print(✔ Python 和 pip 版本正常) return 0 except subprocess.CalledProcessError: print(❌ Python 或 pip 不可用) return 1 return 0关键点是__dsh_context_rules__这个新变量。它必须是顶层list[dict]每个dict包含name: 规则唯一标识trigger: 触发条件目前支持dir_contains文件存在、git_branch分支匹配、env_var环境变量存在pattern: 触发值如pyproject.toml、main、CIaction: 动作run_skill表示调用本插件的main()并传入skill参数skill: 要触发的 Skill 名即main()里args[0]的值message: 匹配成功时的提示语显示在终端。dsh runtime 会在每次 PS1 渲染前即你看到$或提示符前执行一次快照比对。如果当前目录有pyproject.toml且__dsh_context_rules__里有匹配项它就会在你敲下回车前悄悄执行dsh python-env python-env-check并把message输出在提示符上方。4.2 如何调试 Context Hook用dsh context debug看实时快照Context Hook 最让人抓狂的是“为什么没触发”。dsh 提供了内置调试命令# 查看当前目录的完整上下文快照 dsh context debug # 输出示例 # Current Context Snapshot: # - dir: /home/user/my-python-project # - git_branch: main # - dir_contains: [pyproject.toml, README.md] # - env_vars: [HOME, USER, PATH] # - matched_rules: [python-env.pyproject-detected]这个命令会打印出 dsh 实际采集到的所有维度以及当前匹配到的规则名。如果matched_rules是空列表说明你的pattern写错了或者__dsh_context_rules__没被正确加载。常见坑pattern是文件名不是 glob*.toml不会匹配pyproject.toml必须写pyproject.toml__dsh_context_rules__必须和__dsh_plugin__在同一个模块里不能拆到其他文件规则只在active插件里生效dsh plugin list确认状态是ACTIVE。4.3 进阶技巧用.dsh-context文件定义项目级上下文除了自动检测你还可以在项目根目录放一个.dsh-context文件手动声明上下文。它是一个 YAML 文件dsh 会优先读取它# my-project/.dsh-context project_type: django database_url: sqlite:///db.sqlite3 api_base_url: http://localhost:8000然后在插件里你可以通过dsh context get project_type获取这个值或者在context_rules里用trigger: project_config和pattern: django匹配。这相当于给项目打标签让 dsh 的 Skill 能根据项目 DNA 做差异化操作。比如dsh deploy插件检测到project_type: django就执行python manage.py migrate检测到project_type: flask就执行flask db upgrade。这种“项目即配置”的思路正是 dsh 区别于其他 CLI 工具的核心——它把开发环境的语义信息从隐式约定靠人脑记变成了显式声明靠文件存再通过插件自动消费。5. 生产就绪插件打包、分发与内网部署的完整链路写好插件只是第一步。在团队协作或内网环境中你需要一套可靠的分发和更新机制。dsh 原生支持插件市场Market但也可以完全离线运作。我们以log-tail插件为例走一遍从开发到内网落地的全流程。5.1 插件打包用dsh plugin pack生成可移植 tarballdsh 提供了pack命令把插件目录打包成.tar.gz包含所有依赖和元数据cd ~/.dsh/plugins/log-tail/ dsh plugin pack --output log-tail-0.1.0.tar.gz生成的log-tail-0.1.0.tar.gz结构如下log-tail-0.1.0/ ├── plugin.yaml # 自动生成的元数据含 name/version/requirements ├── main.py ├── tailer.py └── README.mdplugin.yaml是关键它由 dsh 根据__dsh_metadata__生成是市场分发的依据。内容类似name: log-tail version: 0.1.0 description: 实时监控服务日志... author: your-name requires: - tail - grep - awk这个 tarball 可以直接发给同事或上传到内网 Nexus/Artifactory 仓库。5.2 内网市场搭建用dsh market serve启动私有仓库dsh 自带一个极简的 HTTP 市场服务器只需一行命令# 在内网服务器上如 192.168.1.100 dsh market serve --port 8080 --plugins-dir /var/dsh-plugins//var/dsh-plugins/目录下放你打包好的.tar.gz文件如log-tail-0.1.0.tar.gz。服务器启动后访问http://192.168.1.100:8080就能看到插件列表。客户端配置# 在开发机上配置 dsh 使用内网市场 dsh config set market.url http://192.168.1.100:8080 # 然后就可以搜索和安装了 dsh market search log-tail dsh market install log-taildsh market install会从内网 URL 下载 tarball校验 SHA256dsh 自动生成并存于plugin.yaml解压到~/.dsh/plugins/installed/并激活。整个过程无需手动 scp 或 U 盘拷贝。5.3 离线部署dsh plugin install支持本地 tarball对于完全断网的环境如金融内网dsh plugin install也支持直接安装 tarball# 把 log-tail-0.1.0.tar.gz 拷贝到目标机器 dsh plugin install ./log-tail-0.1.0.tar.gzdsh 会解压 tarball检查plugin.yaml验证requires然后安装。这是审计友好的方案——所有插件来源可追溯SHA256 可校验无外部网络依赖。5.4 安全加固插件沙箱与权限控制dsh 默认不启用沙箱但生产环境强烈建议开启。通过dsh config set security.sandbox truedsh 会限制插件只能访问--log-dir指定的目录对log-tail这类插件有效禁止插件执行os.system(rm -rf /)等危险调用通过 AST 静态分析对网络请求强制代理可配置内网代理地址。另外dsh plugin install会检查插件签名。你可以用 GPG 对 tarball 签名gpg --detach-sign log-tail-0.1.0.tar.gz # 生成 log-tail-0.1.0.tar.gz.asc然后在客户端导入公钥dsh key import your-key.pub。dsh 安装时会自动验证签名确保插件未被篡改。这套机制让 dsh 插件从“个人脚本”升级为“企业级可治理资产”。它不靠信任而靠密码学和策略引擎。6. 避坑指南那些官方文档不会写的 7 个致命细节最后分享我在真实项目中踩过的、文档里绝不会提的坑。它们不致命但足以让你浪费半天。6.1 插件名冲突dsh命令名是全局唯一的不要和现有 CLI 工具重名你以为dsh git很自然错。dsh会尝试加载git.py插件但系统里已经有/usr/bin/git。dsh 的命令解析器优先匹配插件结果你敲dsh git status它会报错ModuleNotFoundError: No module named git因为它试图import git。解决方案插件名加前缀如dsh-git-status或用dsh gits。6.2requires检查是静态的但which命令可能被 alias 覆盖dsh plugin list显示INACTIVE (missing: jq)但你明明which jq有输出。原因你的 shell 里jq是 alias比如alias jqjq -r。dsh 的requires检查用的是shutil.which(jq)它只认 PATH 中的真实可执行文件不认 alias。解决删掉 alias或用command -v jq确认真实路径。6.3main()的return值会被 dsh 当作 Skill 链的控制信号在dsh run --chain a b c中如果a插件return 1b和c都不会执行。但如果你在a里print(error); return 1dsh 会认为这是“预期失败”不报红只静默终止链。这很好但如果你忘了returnPython 默认return Nonedsh 会把它转