Agent skill

Python Script Integration

by czyt1988 in czyt1988/data-workbench

A skill your agent uses when integrating Python scripts with C++ in a DAWorkbench plugin — calling Python from C++, Python multithreading without UI conflicts, Python-to-C++ data exchange, Python UI…

LGPL-3.0Auto-check passedData & Analytics

Install Python Script Integration

skills CLI
$ npx skills add czyt1988/data-workbench --skill python-script-integration -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install czyt1988/data-workbench python-script-integration --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/czyt1988/data-workbench.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/python-script-integration .claude/skills/python-script-integration && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
python-script-integration
GitHub stars
102
Token cost
~5.5k tokens
SKILL.md length
451 words
Files
1
Skills in repo
4
Repo updated
First seen
Licence
LGPL-3.0

At a glance

A skill your agent uses when integrating Python scripts with C++ in a DAWorkbench plugin — calling Python from C++, Python multithreading without UI conflicts, Python-to-C++ data exchange, Python UI…

  • Works in 4 steps: C++ → Python 调用必须在持有 GIL 时进行。pybind11… → 释放 GIL 让 Python 线程运行:C++ 在等待 Python… → pybind11::error_already_set 异常必须在 GIL… → …
  • Integrating Python scripts with C++ in a DAWorkbench plugin — calling Python from C++
  • SKILL.md covers 决策树, 核心架构, !!!danger GIL 安全规则(所有场景适用) and 场景 A:Python 模块导入与函数调用, plus 6 more sections
  • Calls python

What it does

Python Script Integration is an agent skill from czyt1988/data-workbench. Use when integrating Python scripts with C++ in a DAWorkbench plugin — calling Python from C++, Python multithreading without UI conflicts, Python-to-C++ data exchange, Python UI dialog invocation, threadstatusmanager usage, GIL management. Triggers: Python call, python thread, DAPyModule, pybind11, GIL, callInMainThread, threadstatusmanager, python script, data exchange, python UI.

Its SKILL.md is about 5.5k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It sits in Data & Analytics, covering Async programming. It works with Python and C++. The repository describes itself as: AI Agent-driven data analysis workbench built on C++17/Qt: directed-graph workflow engine, embedded Python (pandas/numpy), interactive publication-quality charts, C++ & Python… The licence is LGPL-3.0.

When your agent uses it

  • Integrating Python scripts with C++ in a DAWorkbench plugin — calling Python from C++
  • Python multithreading without UI conflicts
  • Python-to-C++ data exchange
  • Python UI dialog invocation

Example prompts

  • “/python-script-integration”

Requirements

  • Python 3

Workflow steps

4 steps, taken from the first numbered list in SKILL.md.

  1. C++ → Python 调用必须在持有 GIL 时进行。pybind11 的函数调用会自动获取 GIL,但如果你在 C++ 线程中手动操作 Python 对象,需用 pybind11::gil_scoped_acquire /…
  2. 释放 GIL 让 Python 线程运行:C++ 在等待 Python 后台线程时,必须释放 GIL
  3. pybind11::error_already_set 异常必须在 GIL 作用域内 catch 并消费
  4. Python 后台线程中不要直接操作 Qt 界面控件,必须通过 callInMainThread

What it can do on your machine

Read from SKILL.md and the folder at commit cf3bbda. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • python

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Python Script Integration loads about 5.5k tokens when it runs. Until then it costs about 104 tokens; SKILL.md has 451 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~104
When it runs · the whole SKILL.md, loaded when a task matches
~5.5k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from czyt1988/data-workbench at commit cf3bbda, republished under its LGPL-3.0 licence (© czyt1988). 451 words, ~5,529 tokens.

Download SKILL.mdSave it as .claude/skills/python-script-integration/SKILL.md (or your agent's skills folder).
name
python-script-integration
description
Use when integrating Python scripts with C++ in a DAWorkbench plugin — calling Python from C++, Python multithreading without UI conflicts, Python-to-C++ data exchange, Python UI dialog invocation, thread_status_manager usage, GIL management. Triggers: Python call, python thread, DAPyModule, pybind11, GIL, callInMainThread, thread_status_manager, python script, data exchange, python UI.

Python 脚本与 C++ 集成

本技能指导在 DAWorkbench 插件中实现 Python 脚本调用、Python 多线程、Python 与 C++ 界面的数据交互和跨线程通信。

决策树

你要做什么?
├── C++ 调用 Python 函数 → 场景 A:Python 模块导入与函数调用
├── Python 后台线程处理数据,不阻塞 UI → 场景 B:Python 多线程
├── Python 线程向 C++ 主线程推送数据 → 场景 C:跨线程数据回传
├── Python 脚本访问 C++ 数据管理器 → 场景 D:Python 访问 C++ 数据
├── Python 脚本弹出 UI 对话框 → 场景 E:Python 调用 UI
├── Python 脚本向日志窗口输出 → 场景 F:Python 日志通道
└── Python 脚本部署与更新 → 场景 G:脚本部署

核心架构

┌──────── C++ 主线程(Qt 事件循环) ────────┐
│                                           │
│  MyWorker (C++ Worker)                    │
│    ├ DAPyModule m_pyModule                 │
│    │   └ import("MyPackage")              │
│    ├ DAPyModule m_pyDataAnalysisModule      │
│    │   └ = m_pyModule.attr("data_analysis")│
│    ├ DAPyModule m_threadStatusMgrModule     │
│    │   └ = DAWorkbench.DAPyBase             │
│    │      .attr("thread_status_manager")    │
│    │                                        │
│    ├ 调用 Python 函数 (同步)                │
│    │   └ m_pyDataAnalysisModule->attr(     │
│    │      "process_zip_data_thread")(path)  │
│    │      → 返回 taskid                     │
│    │                                        │
│    └ QTimer::singleShot 轮询线程状态        │
│        └ get_task_status(taskid)           │
│            → 返回 dict {is_running, progress, message, ...} │
│                                           │
└───────────────────────────────────────────┘
          ↑ callInMainThread          ↑ QTimer 轮询
          │ (Qt 信号槽跨线程)           │ (C++ 主动查询)
┌───────── Python 后台线程 ──────────────────┐
│                                           │
│  threading.Thread(target=process_zip_data)│
│    ├ 处理数据(pandas/numpy)              │
│    ├ status.update_progress(p, msg)        │
│    ├ status.update_custom_data(k, v)       │
│    ├ status.finish(success, msg)           │
│    └ 回调函数: signal_handler              │
│        .callInMainThread(add_data_func)    │
│        → 在主线程中操作 DataManager         │
│                                           │
└───────────────────────────────────────────┘

!!!danger GIL 安全规则(所有场景适用)

  1. C++ → Python 调用必须在持有 GIL 时进行。pybind11 的函数调用会自动获取 GIL,但如果你在 C++ 线程中手动操作 Python 对象,需用 pybind11::gil_scoped_acquire / pybind11::gil_scoped_release
  2. 释放 GIL 让 Python 线程运行:C++ 在等待 Python 后台线程时,必须释放 GIL
  3. pybind11::error_already_set 异常必须在 GIL 作用域内 catch 并消费
  4. Python 后台线程中不要直接操作 Qt 界面控件,必须通过 callInMainThread

场景 A:Python 模块导入与函数调用

A1. C++ 导入 Python 模块
cpp
#include "DAPyModule.h"
#include "DAPybind11InQt.h"  // 必须在所有 pybind11 头文件之前
#include "DAPybind11QtCaster.hpp"

bool MyWorker::initializePythonEnv()
{
    try {
        // 1. 导入插件自己的 Python 包
        m_pyModule = std::make_unique<DA::DAPyModule>();
        if (!m_pyModule->import("MyPackage")) {
            daCritical << "Failed to import MyPackage";
            return false;
        }

        // 2. 获取子模块(包内的 .py 文件)
        m_pyDataModule = std::make_unique<DA::DAPyModule>();
        *m_pyDataModule = m_pyModule->attr("data_analysis");

        // 3. 导入平台提供的线程状态管理器
        DA::DAPyModule daWorkbench("DAWorkbench.DAPyBase");
        m_threadStatusMgr = std::make_unique<DA::DAPyModule>();
        *m_threadStatusMgr = daWorkbench.attr("thread_status_manager");

        m_isPythonValid = true;
        return true;
    } catch (const std::exception& e) {
        m_pyModule.reset();
        m_pyDataModule.reset();
        qCritical() << e.what();
        return false;
    }
}
A2. 调用 Python 函数并传递参数
cpp
#include "DAPybind11QtCaster.hpp"  // Qt ↔ Python 类型转换

void MyWorker::callPythonFunction()
{
    try {
        // 获取函数对象
        auto func = m_pyDataModule->attr("my_function");
        if (func.is_none()) {
            qCritical() << "function not found";
            return;
        }

        // 传递 QString 参数(DAPybind11QtCaster 自动转换)
        auto arg = DA::PY::toPyObject(QString("some_path"));
        auto result = func(arg);

        // 获取返回值
        std::string taskid = result.cast<std::string>();
        m_taskID = taskid;

    } catch (const std::exception& e) {
        qCritical() << e.what();
    }
}
A3. Python 包结构
PyScripts/
└── MyPackage/              ← 包名(C++ import 的名字)
    ├── __init__.py         ← 包入口,导出子模块
    ├── data_analysis.py    ← 数据分析脚本
    └── utils.py            ← 工具函数
python
# __init__.py
from . import data_analysis, utils
python
# data_analysis.py
def my_function(path: str) -> str:
    """C++ 通过 attr("my_function") 调用此函数"""
    import pandas as pd
    df = pd.read_csv(path)
    return f"processed_{len(df)}_rows"
A4. DAPybind11QtCaster 自动类型转换
C++ 类型Python 类型说明
QStringstr自动 UTF-8 转换
QList<T>list泛型容器
QHash<K,V> / QMap<K,V>dict字典转换
QVariantAny支持 numpy 数组/标量
QDateTimedatetime.datetime支持 pandas.Timestamp
QColortuple(r,g,b) 或 (r,g,b,a)

手动转换辅助函数:

cpp
// QString → pybind11::object
auto pyStr = DA::PY::toPyObject(QString("hello"));

// pybind11::dict → QVariant
QVariant var = DA::PY::fromPyVariant(pyDict);

// 判断能否转为 QDateTime
bool canConvert = DA::PY::canCastToQDateTime(pyObj);

场景 B:Python 多线程

B1. Python 端:启动后台线程
python
# data_analysis.py
import threading
import DAWorkbench.DAPyBase.thread_status_manager as tsm

def process_zip_data_thread(zip_path: str) -> str:
    """
    主入口:启动后台线程处理数据
    返回: taskid(字符串),空字符串表示启动失败
    """
    try:
        # 创建任务状态追踪器
        taskid, status = tsm.create_task_with_status("process zip data")

        # 启动后台线程
        thread = threading.Thread(
            target=_process_data_internal,
            args=(zip_path, status),
            daemon=True
        )
        thread.start()
        return taskid
    except Exception as e:
        logger.error(f"启动线程失败: {e}")
        return ""

def _process_data_internal(zip_path: str, status: tsm.ProcessingStatus):
    """后台线程的实际处理函数"""
    try:
        status.start()
        status.update_progress(0, "开始处理")
        status.update_custom_data("zip_path", zip_path)

        # ... 数据处理逻辑 ...
        for i, file in enumerate(files):
            status.update_progress(
                (i / len(files)) * 100,
                f"处理文件 {i}/{len(files)}: {file}"
            )
            # ... 处理每个文件 ...

        status.update_custom_data("result_count", len(results))
        status.finish(True, f"完成,共处理 {len(results)} 个文件")
    except Exception as e:
        status.finish(False, f"失败: {e}")
B2. C++ 端:启动线程并轮询状态
cpp
void MyWorker::startBackgroundTask()
{
    if (!isPythonValid()) return;

    try {
        // 调用 Python 函数启动后台线程
        auto func = m_pyDataModule->attr("process_zip_data_thread");
        auto arg = DA::PY::toPyObject(m_zipPath);
        auto result = func(arg);

        if (result.is_none()) return;

        m_taskID = result.cast<std::string>();

        // 显示进度条
        DA::DAStatusBarInterface* statusBar = m_ui->getStatusBar();
        statusBar->showProgressBar();
        statusBar->setProgressText(QString(u8"正在处理..."));

        // 启动轮询
        checkTaskStatus();
    } catch (const std::exception& e) {
        qCritical() << e.what();
    }
}

void MyWorker::checkTaskStatus()
{
    try {
        // 释放 GIL,让 Python 后台线程能运行
        {
            pybind11::gil_scoped_release release;
            QThread::sleep(1);  // 短暂休眠
        }

        // 调用 Python 获取任务状态
        auto get_status = m_threadStatusMgr->attr("get_task_status");
        pybind11::dict status = get_status(m_taskID);

        bool is_running = status["is_running"].cast<bool>();

        if (is_running) {
            // 更新进度
            double progress = status["progress"].cast<double>();
            std::string message = status["message"].cast<std::string>();
            double elapsed = status["elapsed_seconds"].cast<double>();

            DA::DAStatusBarInterface* statusBar = m_ui->getStatusBar();
            statusBar->setProgress(progress);
            statusBar->setProgressText(
                QString::fromStdString(message) +
                QString(u8" 已用时: %1:%2")
                    .arg(static_cast<int>(elapsed) / 60, 2, 10, QChar('0'))
                    .arg(static_cast<int>(elapsed) % 60, 2, 10, QChar('0'))
            );

            // 继续轮询(20ms 后再次检查)
            QTimer::singleShot(20, this, &MyWorker::checkTaskStatus);
        } else {
            // 任务完成
            bool is_success = status["is_success"].cast<bool>();
            QString msg = QString::fromStdString(status["message"].cast<std::string>());

            DA::DAStatusBarInterface* statusBar = m_ui->getStatusBar();
            statusBar->hideProgressBar();
            statusBar->setBusy(false);

            if (is_success) {
                statusBar->showMessage(QString(u8"完成!"));
                // 获取自定义数据
                pybind11::dict custom_data = status["custom_data"];
                QString zip_path = QString::fromStdString(
                    custom_data["zip_path"].cast<std::string>());
                // 处理完成后的逻辑
            } else {
                statusBar->showMessage(QString(u8"失败! ") + msg);
            }

            QApplication::processEvents();
        }
    } catch (const std::exception& e) {
        qCritical() << e.what();
    }
}
B3. 状态字典字段说明

get_task_status(taskid) 返回的字典包含以下字段:

字段类型说明
task_idstr任务唯一 ID
task_namestr任务名称
is_runningbool是否正在运行(未暂停、未取消、未完成)
is_pausedbool是否暂停
is_canceledbool是否取消
is_successbool是否成功完成
current_stagestr当前阶段描述
progressfloat进度百分比 (0-100)
elapsed_secondsfloat已用时间(秒)
messagestr当前状态消息
custom_datadict自定义数据副本
B4. ProcessingStatus API 速查
python
# 创建任务
taskid, status = tsm.create_task_with_status("task name")

# 状态控制
status.start()                    # 开始
status.update_progress(50, "处理中")  # 更新进度
status.pause()                    # 暂停
status.resume()                   # 恢复
status.cancel("用户取消")           # 取消
status.finish(True, "完成消息")     # 完成(成功/失败)

# 自定义数据
status.update_custom_data("key", value)
value = status.get_custom_data("key", default)

# 查询
info = status.get_status()        # 返回状态字典
is_active = status.is_active()    # 是否活跃
is_finished = status.is_finished() # 是否已结束

场景 C:跨线程数据回传

Python 后台线程处理完数据后,需要将结果写入 C++ 的 DataManager。但不能直接操作 Qt 界面,必须通过 DAPythonSignalHandler::callInMainThread 在主线程执行。

C1. Python 端:通过 callInMainThread 回传数据
python
# data_analysis.py
import da_app, da_data

def process_zip_data_thread(zip_path: str) -> str:
    taskid, status = tsm.create_task_with_status("process zip data")

    def internal_callback(result_dict):
        """后台线程完成后的回调,通过 callInMainThread 在主线程写入数据"""
        if result_dict is not None:
            signal_handler = da_app.getCore().getPythonSignalHandler()
            if signal_handler:
                def add_data_in_main_thread():
                    """此函数在 Qt 主线程中执行"""
                    datamanager = da_app.getCore().getDataManagerInterface()
                    for name, df in result_dict.items():
                        data = da_data.DAData(df)
                        data.setName(name)
                        data.setDescribe(name)
                        datamanager.addData(data)

                signal_handler.callInMainThread(add_data_in_main_thread)

    thread = threading.Thread(
        target=_process_data,
        args=(zip_path, internal_callback, status),
        daemon=True
    )
    thread.start()
    return taskid
C2. C++ 端:监听数据添加信号
cpp
// 在 Worker 初始化时连接数据添加信号
void MyWorker::initialize(DA::DACoreInterface* core, MyUI* ui)
{
    m_core = core;
    m_ui = core->getUiInterface();

    DA::DADataManagerInterface* datamgr = m_core->getDataManagerInterface();
    // 连接数据添加信号
    connect(datamgr, &DA::DADataManagerInterface::dataAdded,
            this, &MyWorker::onDataAdded);
}

void MyWorker::onDataAdded()
{
    // 数据已通过 Python 线程 → callInMainThread → DataManager::addData 添加
    // 在这里重新分析数据
    analysisDatas();
    Q_EMIT datasAdded();  // 通知 UI 更新
}
C3. callInMainThread 机制详解
Python 后台线程                     C++ 主线程(Qt 事件循环)
    │                                     │
    ├ signal_handler.callInMainThread(    │
    │   lambda: add_data())               │
    │   └ 函数包装为 FunctionWrapper       │
    │   └ 存入 m_functionMap (mutex)       │
    │   └ emit executeRequested(id) ──────┼──→ Qt 跨线程队列连接
    │                                     │
    │   (Python 线程继续执行)              ├ onExecuteRequested(id)
    │                                     │   └ 从 map 取出 FunctionWrapper
    │                                     │   └ 执行函数(操作 DataManager)
    │                                     │

场景 D:Python 访问 C++ 数据

D1. 从 DataManager 查找数据
python
import da_app, da_data

def my_python_function():
    """Python 脚本中访问 C++ DataManager"""
    datamanager = da_app.getCore().getDataManagerInterface()

    # 按通配符查找数据表
    datas = datamanager.findDatas("*module*")
    for d in datas:
        name = d.getName()
        df = d.toDataFrame()  # 转为 pandas DataFrame
        # 处理 DataFrame...

    # 获取特定名称的数据
    datas = datamanager.findDatas("*module_8")
    if datas:
        df = datas[0].toDataFrame()
D2. 向 DataManager 写入数据
python
import da_app, da_data
import pandas as pd

def write_data_to_manager():
    """将 DataFrame 写入 DataManager(必须在主线程调用)"""
    signal_handler = da_app.getCore().getPythonSignalHandler()
    signal_handler.callInMainThread(lambda: _do_write())

def _do_write():
    datamanager = da_app.getCore().getDataManagerInterface()
    df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
    data = da_data.DAData(df)
    data.setName("my_data_table")
    data.setDescribe("description")
    datamanager.addData(data)
D3. DAData 在 Python 中的操作
python
import da_data

# DAData 包装 pandas DataFrame
data = da_data.DAData(df)    # 从 DataFrame 创建
df = data.toDataFrame()      # 转回 DataFrame
data.setName("name")        # 设置名称
data.setDescribe("desc")    # 设置描述
name = data.getName()        # 获取名称
is_null = data.isNull()     # 是否为空
data_id = data.id()          # 获取 ID

场景 E:Python 调用 UI

E1. 弹出文件选择对话框
python
import da_app

def my_function():
    ui = da_app.getCore().getUiInterface()

    # 选择文件夹
    path = ui.getExistingDirectory("选择保存路径")
    if len(path) == 0:
        return  # 用户取消

    # 选择文件
    # file_path = ui.getOpenFileName("选择文件", "CSV (*.csv)")
E2. 弹出表单配置对话框

平台提供 FormBuilder 用于构建参数配置对话框,C++ 端通过 getConfigValues 弹出对话框。

python
from DAWorkbench.DAPyBase.form_builder import FormBuilder, option

def show_config_dialog():
    ui = da_app.getCore().getUiInterface()

    config = (FormBuilder("参数设置")
        .group("basic", "基本设置")
        .enum("machine", label="机型",
              default="GMV-224WM/S",
              options=[option("GMV-224WM/S", "GMV-224WM/S"),
                       option("GMV-252WM/S", "GMV-252WM/S")],
              description="选择机型")
        .bool("save_word", label="是否生成报告",
              default=True,
              description="勾选将生成 Word 报告")
        .end_group()
        .build()
    )

    result = ui.getConfigValues(config, "my_config_cache_key")
    if len(result) == 0:
        return  # 用户取消

    machine = result["machine"]
    save_word = result["save_word"]
E3. FormBuilder API 速查
方法用途
FormBuilder(title)创建表单构建器
.group(id, title)开始一个分组
.end_group()结束当前分组
.str(name, label, default, description)字符串输入
.int(name, label, default, description)整数输入
.float(name, label, default, description)浮点输入
.bool(name, label, default, description)布尔选择
.enum(name, label, default, options, description)下拉枚举
.option(value, label)枚举选项(在 options 列表中)
.build()构建 JSON 配置

场景 F:Python 日志通道

Show full SKILL.md (186 more words)Show less
F1. 向界面日志窗口输出
python
import da_app

def my_function():
    ui = da_app.getCore().getUiInterface()

    # 第二个参数 showInStatusBar:
    #   True = 同时显示在状态栏(仅主线程安全)
    #   False = 仅写入日志窗口(后台线程安全)
    ui.addInfoLogMessage("信息消息", False)
    ui.addWarningLogMessage("警告消息", False)
    ui.addCriticalLogMessage("错误消息", False)

!!!danger 后台线程中必须传 showInStatusBar=False Python 后台线程中调用日志方法时,第二个参数必须为 False,否则会触发 GUI 操作导致崩溃。False 表示仅走 spdlog 通道(线程安全),不触碰状态栏等 GUI 控件。

F2. 完整的后台线程日志封装
python
import logging
from loguru import logger

try:
    import da_app
    _has_da_app = True
except Exception:
    _has_da_app = False

def emit_ui_message(msg: str, level: str = "warning") -> None:
    """线程安全的 UI 日志输出"""
    if not _has_da_app:
        return
    try:
        ui = da_app.getCore().getUiInterface()
        if level == "critical":
            ui.addCriticalLogMessage(msg, False)
        elif level == "info":
            ui.addInfoLogMessage(msg, False)
        else:
            ui.addWarningLogMessage(msg, False)
    except Exception:
        pass  # UI 推送失败不应影响数据处理主流程

场景 G:脚本部署

G1. CMake 部署(配置阶段自动复制)
cmake
# src/CMakeLists.txt
# Python 脚本在 CMake 配置阶段自动复制到安装目录
file(COPY "${CMAKE_CURRENT_LIST_DIR}/PyScripts/MyPackage"
     DESTINATION ${DAWorkbench_INSTALL_DIR}/bin/PyScripts)
install(DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/PyScripts/MyPackage"
        DESTINATION ${CMAKE_INSTALL_BINDIR}/PyScripts)
G2. 运行时目录结构
bin_Release_qtX.Y.Z_MSVC_x64/bin/
├── PyScripts/
│   ├── DAWorkbench/          ← 平台内置 Python 包
│   │   └── DAPyBase/
│   │       └── thread_status_manager.py
│   └── MyPackage/            ← 插件 Python 包
│       ├── __init__.py
│       └── data_analysis.py
├── plugins/
│   └── MyPlugin.dll          ← 插件 DLL
└── DAWorkbench.exe            ← 主程序
G3. 修改 Python 脚本后的生效方式

!!!danger 修改 .py 文件后必须重新部署并重启应用 DAWorkbench 嵌入式 Python 在启动时加载脚本到内存,修改 .py 文件后: 1. 重新运行 CMake configure(触发 file(COPY) 复制到 bin 目录),或手动复制 .py 到 bin/PyScripts/MyPackage/ 2. 重启 DAWorkbench 主程序(Python 解释器重新初始化) 3. 使用 DAPyModule::reload() 可在运行时重新加载模块,但仅对已导入的模块有效

完整调用链示例

以下是一个完整的"C++ 触发 → Python 后台处理 → C++ 轮询进度 → Python 回传数据 → C++ 更新界面"的链路:

1. 用户点击 Ribbon Action
   → C++ MyUI::onActionTriggered()
   → MyWorker::importZipData()

2. C++ 调用 Python 启动后台线程
   → m_pyDataAnalysisModule->attr("process_zip_data_thread")(zipPath)
   → Python: threading.Thread(target=_process_data).start()
   → 返回 taskid

3. C++ 启动 QTimer 轮询
   → checkZipDataImportStatus()
   → 释放 GIL: pybind11::gil_scoped_release + QThread::sleep(1)
   → 获取状态: get_task_status(taskid) → dict
   → 更新状态栏: statusBar->setProgress(progress)
   → QTimer::singleShot(20, continue)

4. Python 后台线程处理数据
   → pandas 读取 CSV → 清洗 → 时间对齐 → 统计计算
   → status.update_progress(x, "处理中")
   → status.update_custom_data("key", value)

5. Python 线程完成,回调函数通过 callInMainThread 写入数据
   → signal_handler.callInMainThread(add_data_func)
   → Qt 信号 → 主线程执行: datamanager.addData(data)

6. C++ 收到 dataAdded 信号
   → onDataAdded() → analysisDatas()
   → Q_EMIT datasAdded()

7. UI 收到 datasAdded 信号
   → MyUI::onDataAdded()
   → 更新设备选择列表
   → 创建默认绘图

C++ 侧 Python 对象管理

DAPyModule 使用模式
cpp
// 成员变量声明(使用 unique_ptr 延迟初始化)
std::unique_ptr<DA::DAPyModule> m_pyModule;
std::unique_ptr<DA::DAPyModule> m_pyDataModule;
std::unique_ptr<DA::DAPyModule> m_threadStatusMgr;

// 初始化
m_pyModule = std::make_unique<DA::DAPyModule>();
m_pyModule->import("MyPackage");

// 获取子模块
m_pyDataModule = std::make_unique<DA::DAPyModule>();
*m_pyDataModule = m_pyModule->attr("data_analysis");

// 调用函数
auto func = m_pyDataModule->attr("my_function");
auto result = func(arg1, arg2);

// 从 dict 读取值
pybind11::dict status = get_status(taskid);
bool running = status["is_running"].cast<bool>();
double progress = status["progress"].cast<double>();
std::string message = status["message"].cast<std::string>();
pybind11::dict custom_data = status["custom_data"];
头文件引入顺序
cpp
// 1. Qt 头文件(正常引入)
#include <QObject>
#include <QString>

// 2. pybind11 桥接头(必须在这些之前引入所有 Qt 头文件)
#include "DAPybind11InQt.h"        // slots 宏冲突解决
#include "DAPybind11QtCaster.hpp"  // Qt ↔ Python 类型转换

// 3. DA Python 封装
#include "DAPyModule.h"

// 4. 其他
#include "DALog.h"

常见陷阱

  1. 后台线程中不能直接操作 Qt 控件 — 必须通过 callInMainThread
  2. GIL 死锁 — C++ 在等待 Python 线程时必须释放 GIL (gil_scoped_release)
  3. 日志 showInStatusBar — 后台线程必须传 False
  4. Python 脚本修改后不生效 — 需要复制到 bin 目录并重启应用
  5. DAPybind11InQt.h 必须第一个引入 — 解决 Qt slots 宏与 Python.h 的冲突
  6. DAWidgets 需要显式 find_package — DAGui 依赖它但不自动加载
  7. Python 异常必须在 GIL 作用域内 catch — pybind11::error_already_set 的析构需要 GIL
  8. DAPyModule 继承自 DAPyObjectWrapper(非 QObject) — 不能使用 Qt 信号槽

参考文件

文件说明
src/MyWorker.cppC++ 调用 Python + QTimer 轮询线程状态完整示例
src/PyScripts/MyPackage/data_analysis.pyPython 多线程 + callInMainThread 回传数据完整示例
src/PyScripts/MyPackage/zip_csv_file_handle.py后台线程处理数据 + thread_status_manager 使用示例
src/PyScripts/MyPackage/plot_map.pyPython 写入 DataManager + 生成 Word 报告示例
data-workbench/src/DAPyBindQt/DAPyModule.hDAPyModule 基类 API
data-workbench/src/DAPyBindQt/DAPybind11InQt.hslots 宏冲突解决
data-workbench/src/DAPyBindQt/DAPybind11QtCaster.hppQt ↔ Python 类型转换器
data-workbench/src/DAPyBindQt/DAPythonSignalHandler.h跨线程通信机制
data-workbench/src/DAInterface/DACoreInterface.h核心接口(含 getPythonSignalHandler)
data-workbench/src/PyScripts/DAWorkbench/DAPyBase/thread_status_manager.py线程状态管理器完整 API
data-workbench/skills/add-python-binding/SKILL.md添加 Python 绑定(暴露 C++ 给 Python)的技能

© czyt1988, LGPL-3.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in skills/python-script-integration of czyt1988/data-workbench.

Open the folder on GitHubat commit cf3bbda

Compare with similar skills

Python Script Integration next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Python Script Integration compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Python Script Integration this skillczyt1988/data-workbench102—~5.5kAutomated safety check: PassLGPL-3.0
Replay Oriented InstrumentationArabelaTso/Skills-4-SE253—~2.5kAutomated safety check: PassApache-2.0
Onnxtxtonnx/onnx22k—~1.3kAutomated safety check: PassApache-2.0
Smooth Cpp Sharedopenforecast-org/smooth107—~1.1kAutomated safety check: PassLGPL-2.1
Elodin DBelodin-sys/elodin547—~2.8kAutomated safety check: PassApache-2.0
GeomasterLeonChaoX/qinyan-academic-skills9371 repos~2.9kAutomated safety check: PassMIT

Similar skills

  • Replay Oriented Instrumentation

    ArabelaTso/Skills-4-SE

    Instruments programs to record execution information for deterministic replay debugging.

    253 GitHub stars~2.5k tokensUpdated 1 mo ago
    DevelopmentAuto-check passed
  • Onnxtxt

    onnx/onnx

    Read or write ONNX text format ("onnxtxt"). An agent skill from onnx/onnx.

    22k GitHub stars~1.3k tokensUpdated today
    AI & LLM EngineeringAuto-check passed
  • Smooth Cpp Shared

    openforecast-org/smooth

    Work on the C++ layer that the R package and the Python port share — the headers under src/headers/, the Rcpp bindings in src/, the pybind11 bindings in src/python/, and the two build systems that…

    107 GitHub stars~1.1k tokensUpdated yesterday
    Data & AnalyticsAuto-check passed
  • Elodin DB

    elodin-sys/elodin

    Work with Elodin-DB, the time-series telemetry database. An agent skill from elodin-sys/elodin.

    547 GitHub stars~2.8k tokensUpdated today
    Data & AnalyticsAuto-check passed
  • Geomaster

    LeonChaoX/qinyan-academic-skills

    Comprehensive geospatial science skill covering remote sensing, GIS, spatial analysis, machine learning for earth observation, and 30+ scientific domains.

    937 GitHub starsUsed in 1 repo~2.9k tokens
    Data & AnalyticsAuto-check passed
  • Make Op Verify

    CVCUDA/CV-CUDA

    Verify a new CV-CUDA operator against the deterministic final regression checklist (the /make-op done-gate).

    2.7k GitHub stars~433 tokensUpdated 21 days ago
    AI & LLM EngineeringAuto-check passed

More from czyt1988/data-workbench

  • Add Python Binding

    czyt1988/data-workbench

    A skill your agent uses when adding new Python bindings (exposing C++ classes/functions to Python) in the DAWorkBench project, or modifying existing bindings.

    102 GitHub stars~4.6k tokensUpdated 25 days ago
    Auto-check passed
  • Create Setting Panel

    czyt1988/data-workbench

    A skill your agent uses when creating property panels or configuration UI in the data-workbench Qt/C++ project.

    102 GitHub stars~3.2k tokensUpdated 25 days ago
    Auto-check passed
  • Create Cpp Plugin

    czyt1988/data-workbench

    A skill your agent uses when creating a new C++ plugin DLL for the data-workbench platform, or modifying an existing plugin's structure, CMake, UI integration, or data access.

    102 GitHub stars~5.5k tokensUpdated 25 days ago
    Auto-check passed

Works with

Questions about Python Script Integration

What does Python Script Integration do?

A skill your agent uses when integrating Python scripts with C++ in a DAWorkbench plugin — calling Python from C++, Python multithreading without UI conflicts, Python-to-C++ data exchange, Python UI…. Python Script Integration is an agent skill from czyt1988/data-workbench. Use when integrating Python scripts with C++ in a DAWorkbench plugin — calling Python from C++, Python multithreading without UI conflicts, Python-to-C++ data exchange, Python UI dialog invocation, threadstatusmanager usage, GIL management.

When should I use Python Script Integration?

Python Script Integration fits situations like: integrating Python scripts with C++ in a DAWorkbench plugin — calling Python from C++; Python multithreading without UI conflicts; Python-to-C++ data exchange; Python UI dialog invocation.

How do I install Python Script Integration in Claude Code?

Run `npx skills add czyt1988/data-workbench --skill python-script-integration -a claude-code`. Or copy the skill folder (skills/python-script-integration in czyt1988/data-workbench) into .claude/skills/python-script-integration in your project. Claude Code loads it when a task matches its description.

How do I install Python Script Integration in Codex?

Run `npx skills add czyt1988/data-workbench --skill python-script-integration -a codex`. Or copy the skill folder (skills/python-script-integration in czyt1988/data-workbench) into .agents/skills/python-script-integration in your project. Codex loads it when a task matches its description.

Can I use Python Script Integration in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add czyt1988/data-workbench --skill python-script-integration -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/python-script-integration, .gemini/skills/python-script-integration, .github/skills/python-script-integration and .opencode/skills/python-script-integration in your project.

What does Python Script Integration need to run?

Going by SKILL.md and its folder, Python Script Integration needs the command-line tools its instructions call (python). Our summary lists: Python 3.

Does Python Script Integration access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Python Script Integration safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Python Script Integration use?

Python Script Integration is published under the LGPL-3.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Python Script Integration use?

About 5.5k tokens (SKILL.md is roughly 22k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Python Script Integration?

Skills that share tags, products or a category with Python Script Integration: Replay Oriented Instrumentation (ArabelaTso/Skills-4-SE, 253 stars), Onnxtxt (onnx/onnx, 22k stars), Smooth Cpp Shared (openforecast-org/smooth, 107 stars) and Elodin DB (elodin-sys/elodin, 547 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Python Script Integration?

czyt1988 (a GitHub user) maintains it in czyt1988/data-workbench, which has 102 GitHub stars. The repository holds 4 skills in this directory. The repository was last updated on September 12, 2026.

Source: czyt1988/data-workbench on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.