complete_and_exit

本文详细介绍了Linux内核中的Completion机制,这是一种轻量级的同步机制,允许一个线程通知另一个线程任务已完成。文章通过示例代码展示了如何创建、初始化及使用Completion,并提供了使用complete_all接口的变种示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

  struct __wait_queue_head {
          spinlock_t lock;
          struct list_head task_list;
  };
 typedef struct __wait_queue_head wait_queue_head_t;
  struct completion {
          unsigned int done;
          wait_queue_head_t wait;
  };

 

 

 

 void fastcall complete(struct completion *x)
 {
         unsigned long flags;
 
         spin_lock_irqsave(&x->wait.lock, flags);
         x->done++;
         __wake_up_common(&x->wait, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE,
                          1, 0, NULL);
         spin_unlock_irqrestore(&x->wait.lock, flags);
 }

 completion是一种轻量级的机制,它允许一个线程告诉另一个线程工作已经完成。可以利用下面的宏静态创建completion:
                         DECLARE_COMPLETION(my_completion);
       

        如果运行时创建completion,则必须采用以下方法动态创建和初始化:
                         struct compltion my_completion;
                          init_completion(&my_completion);

        completion的相关定义包含在kernel/include/linux/completion.h中:

                        struct completion {
                                     unsigned int done;
                                     wait_queue_head_t wait;
                         };


#define COMPLETION_INITIALIZER(work) /
                                                           { 0, __WAIT_QUEUE_HEAD_INITIALIZER((work).wait) }

#define DECLARE_COMPLETION(work) /
                                                      struct completion work = COMPLETION_INITIALIZER(work)

static inline void init_completion(struct completion *x)
{
          x->done = 0;
          init_waitqueue_head(&x->wait);
}

 

       要等待completion,可进行如下调用:
                    void wait_for_completion(struct completion *c);

       触发completion事件,调用:
                   void complete(struct completion *c);    //唤醒一个等待线程
                   void complete_all(struct completion *c);//唤醒所有的等待线程

        为说明completion的使用方法,将《Linux设备驱动程序》一书中的complete模块的代码摘抄如下:
/*
* complete.c -- the writers awake the readers
*
* Copyright (C) 2003 Alessandro Rubini and Jonathan Corbet
* Copyright (C) 2003 O'Reilly & Associates
*
* The source code in this file can be freely used, adapted,
* and redistributed in source or binary form, so long as an
* acknowledgment appears in derived source files.    The citation
* should list that the code comes from the book "Linux Device
* Drivers" by Alessandro Rubini and Jonathan Corbet, published
* by O'Reilly & Associates.     No warranty is attached;
* we cannot take responsibility for errors or fitness for use.
*
* $Id: complete.c,v 1.2 2004/09/26 07:02:43 gregkh Exp $
*/

#include <linux/module.h>
#include <linux/init.h>

#include <linux/sched.h>   /* current and everything */
#include <linux/kernel.h> /* printk() */
#include <linux/fs.h>      /* everything... */
#include <linux/types.h>   /* size_t */
#include <linux/completion.h>

MODULE_LICENSE("Dual BSD/GPL");

static int complete_major = 253;//指定主设备号

DECLARE_COMPLETION(comp);

ssize_t complete_read (struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
         printk(KERN_DEBUG "process %i (%s) going to sleep/n",
         current->pid, current->comm);
         wait_for_completion(&comp);
         printk(KERN_DEBUG "awoken %i (%s)/n", current->pid, current->comm);
         return 0; /* EOF */
}

ssize_t complete_write (struct file *filp, const char __user *buf, size_t count,
    loff_t *pos)
{
         printk(KERN_DEBUG "process %i (%s) awakening the readers.../n",
         current->pid, current->comm);
         complete(&comp);
         return count; /* succeed, to avoid retrial */
}


struct file_operations complete_fops = {
         .owner = THIS_MODULE,
         .read =    complete_read,
         .write = complete_write,
};


int complete_init(void)
{
         int result;

/*
    * Register your major, and accept a dynamic number
    */
        result = register_chrdev(complete_major, "complete", &complete_fops);
        if (result < 0)
                return result;
        if (complete_major == 0)
                complete_major = result; /* dynamic */
        return 0;
}

void complete_cleanup(void)
{
         unregister_chrdev(complete_major, "complete");
}

module_init(complete_init);
module_exit(complete_cleanup);


        该模块定义了一个简单的completion设备:任何试图从该设备中读取的进程都将等待,直到其他设备写入该设备为止。编译此模块的Makefile如下:
obj-m := complete.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
rm -f *.ko *.o *.mod.c

在linux终端中执行以下命令,编译生成模块,并进行动态加载。
#make
#mknod completion c 253 0
#insmod complete.ko
再打开三个终端,一个用于读进程:
#cat completion
一个用于写进程:
#echo >completion
另一个查看系统日志:
#tail -f /var/log/messages

         值得注意的是,当我们使用的complete_all接口时,如果要重复使用一个completion结构,则必须执行INIT_COMPLETION(struct completion c)来重新初始化它。可以在kernel/include/linux/completion.h中找到这个宏的定义:
          #define INIT_COMPLETION(x) ((x).done = 0)

        以下代码对书中原有的代码进行了一番变动,将唤醒接口由原来的complete换成了complete_all,并且为了重复利用completion结构,所有读进程都结束后就重新初始化completion结构,具体代码如下:
#include <linux/module.h>
#include <linux/init.h>

#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/completion.h>

MODULE_LICENSE("Dual BSD/GPL");

#undef KERN_DEBUG
#define KERN_DEBUG "<1>"

static int complete_major=253;
static int reader_count = 0;

DECLARE_COMPLETION(comp);

ssize_t complete_read (struct file *filp,char __user *buf,size_t count,loff_t *pos)
{
           printk(KERN_DEBUG "process %i (%s) going to sleep,waiting for writer/n",current->pid,current->comm);
           reader_count++;
           printk(KERN_DEBUG "In read ,before comletion: reader count = %d /n",reader_count);
           wait_for_completion(&comp);
           reader_count--;
           printk(KERN_DEBUG "awoken %s (%i) /n",current->comm,current->pid);
           printk(KERN_DEBUG "In read,after completion : reader count = %d /n",reader_count);

/*如果使用complete_all,则completion结构只能用一次,再次使用它时必须调用此宏进行重新初始化*/
           if(reader_count == 0)
                       INIT_COMPLETION(comp);

           return 0;
}

ssize_t complete_write(struct file *filp,const char __user *buf,size_t count,loff_t *pos)
{
           printk(KERN_DEBUG "process %i (%s) awoking the readers.../n",current->pid,current->comm);
           printk(KERN_DEBUG "In write ,before do complete_all : reader count = %d /n",reader_count);

           if(reader_count != 0)  
                   complete_all(&comp);

           printk(KERN_DEBUG "In write ,after do complete_all : reader count = %d /n",reader_count);

           return count;
}

struct file_operations complete_fops={
           .owner = THIS_MODULE,
           .read = complete_read,
           .write = complete_write,
};

int complete_init(void)
{
           int result;

           result=register_chrdev(complete_major,"complete",&complete_fops);
           if(result<0)
                    return result;
           if(complete_major==0)
                   complete_major =result;

           printk(KERN_DEBUG    "complete driver test init! complete_major=%d/n",complete_major);
           printk(KERN_DEBUG "静态初始化completion/n");

           return 0;
}

void complete_exit(void)
{
           unregister_chrdev(complete_major,"complete");
           printk(KERN_DEBUG    "complete driver    is removed/n");
}

module_init(complete_init);
module_exit(complete_exit);

这里测试步骤和上述一样,只不过需要多打开几个终端来执行多个进程同时读操作。

____________

参考资料:
1.Jonathan Corbet等著,魏永明等译.linux设备驱动程序(第三版)
2.Linux Kernel 

 

 

 

 #define spin_lock_irqsave(lock, flags) /
 do { /
         local_irq_save(flags); /
         preempt_disable(); /
         _raw_spin_lock_flags(lock, flags); /
 } while (0)

 

 

 

 static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
                              int nr_exclusive, int sync, void *key)
 {
         struct list_head *tmp, *next;
 
         list_for_each_safe(tmp, next, &q->task_list) {
                 wait_queue_t *curr;
                 unsigned flags;
                 curr = list_entry(tmp, wait_queue_t, task_list);
                 flags = curr->flags;
                 if (curr->func(curr, mode, sync, key) &&
                     (flags & WQ_FLAG_EXCLUSIVE) &&
                     !--nr_exclusive)
                         break;
         }
 }
 asmlinkage NORET_TYPE void do_exit(long code)
 {
         struct task_struct *tsk = current;
 
         if (unlikely(in_interrupt()))
                 panic("Aiee, killing interrupt handler!");
         if (unlikely(!tsk->pid))
                 panic("Attempted to kill the idle task!");
         if (unlikely(tsk->pid == 1))
                 panic("Attempted to kill init!");
         if (tsk->io_context)
                 exit_io_context();
         tsk->flags |= PF_EXITING;
         del_timer_sync(&tsk->real_timer);
 
         if (unlikely(in_atomic()))
                 printk(KERN_INFO "note: %s[%d] exited with preempt_count %d/n",
                                 current->comm, current->pid,
                                 preempt_count());
 
         profile_exit_task(tsk);
  
         if (unlikely(current->ptrace & PT_TRACE_EXIT)) {
                 current->ptrace_message = code;
                 ptrace_notify((PTRACE_EVENT_EXIT << 8) | SIGTRAP);
         }
 
         acct_process(code);
         __exit_mm(tsk);
 
         exit_sem(tsk);
         __exit_files(tsk);
         __exit_fs(tsk);
         exit_namespace(tsk);
         exit_thread();
 
         if (tsk->signal->leader)
                 disassociate_ctty(1);
 
         module_put(tsk->thread_info->exec_domain->module);
         if (tsk->binfmt)
                 module_put(tsk->binfmt->module);
 
         tsk->exit_code = code;
         exit_notify(tsk);
 #ifdef CONFIG_NUMA
         mpol_free(tsk->mempolicy);
         tsk->mempolicy = NULL;
 #endif
         schedule();
         BUG();
         /* Avoid "noreturn function does return".  */
         for (;;) ;
 }
 
 NORET_TYPE void complete_and_exit(struct completion *comp, long code)
 {
         if (comp)
                 complete(comp);
 
         do_exit(code);
 }
 
 

 

 

再加一个功能:1.创建一个按钮显示“完成首拼”,再新创建的excel文件里,会自动生成首拼,用python代码实现import sys import pandas as pd from 连接池到实验 import * from PyQt5.QtWidgets import ( QApplication, QWidget, QLabel, QLineEdit, QPushButton, QFileDialog, QMessageBox, QVBoxLayout, QComboBox, QHBoxLayout ) import numpy as np import os import tempfile import win32com.client as win32 # 用于操作Excel # 主窗口类 class ExcelImporter(QWidget): def __init__(self): super().__init__() self.setWindowTitle("Excel 导入 MySQL 工具") self.resize(600, 400) # 增加窗口高度以容纳新控件 self.last_created_excel = None # 记录最后创建的Excel文件路径 self.templates = { "客户信息": [ "姓名","首拼"], "产品信息": [ "产品名称", "首拼"], "订单记录": ["客户名","首拼"] } self.init_ui() def init_ui(self): layout = QVBoxLayout() # 模板选择区域 layout.addWidget(QLabel("选择模板:")) template_layout = QHBoxLayout() self.template_combo = QComboBox() self.template_combo.addItems(self.templates.keys()) template_layout.addWidget(self.template_combo) self.create_button = QPushButton("创建Excel") self.create_button.clicked.connect(self.create_excel_template) template_layout.addWidget(self.create_button) self.complete_button = QPushButton("我已编辑完成") self.complete_button.clicked.connect(self.on_complete_clicked) self.complete_button.setEnabled(False) # 初始禁用 template_layout.addWidget(self.complete_button) layout.addLayout(template_layout) # 文件路径输入框 layout.addWidget(QLabel("选择 Excel 文件:")) self.file_path_edit = QLineEdit() self.browse_button = QPushButton("浏览") self.browse_button.clicked.connect(self.select_file) file_layout = QHBoxLayout() file_layout.addWidget(self.file_path_edit) file_layout.addWidget(self.browse_button) layout.addLayout(file_layout) # 表名输入框 layout.addWidget(QLabel("目标数据库表名:")) self.table_name_edit = QLineEdit() layout.addWidget(self.table_name_edit) # 导入按钮 self.import_button = QPushButton("导入数据库") self.import_button.clicked.connect(self.import_to_database) layout.addWidget(self.import_button) self.setLayout(layout) def create_excel_template(self): """创建Excel模板并打开""" try: # 获取选中的模板名称 template_name = self.template_combo.currentText() # 创建临时Excel文件 with tempfile.NamedTemporaryFile(suffix='.xlsx', delete=False) as tmp: file_path = tmp.name self.last_created_excel = file_path # 创建DataFrame并写入列标题 headers = self.templates[template_name] df = pd.DataFrame(columns=headers) df.to_excel(self.last_created_excel, index=False) # 使用Excel 2013打开文件 excel = win32.gencache.EnsureDispatch('Excel.Application') excel.Visible = True workbook = excel.Workbooks.Open(os.path.abspath(self.last_created_excel)) # 启用"我已编辑完成"按钮 self.complete_button.setEnabled(True) QMessageBox.information(self, "成功", f"Excel模板已创建,请编辑后点击'我已编辑完成'") except Exception as e: QMessageBox.critical(self, "错误", f"创建Excel失败: {str(e)}") def on_complete_clicked(self): """处理编辑完成按钮点击事件""" if self.last_created_excel and os.path.exists(self.last_created_excel): self.file_path_edit.setText(self.last_created_excel) self.table_name_edit.setText(self.template_combo.currentText()) self.complete_button.setEnabled(False) # 重置按钮状态 else: QMessageBox.warning(self, "警告", "未找到创建的Excel文件") def select_file(self): file_path, _ = QFileDialog.getOpenFileName(self, "选择 Excel 文件", "", "Excel 文件 (*.xlsx *.xls)") if file_path: self.file_path_edit.setText(file_path) def import_to_database(self): file_path = self.file_path_edit.text() table_name = self.table_name_edit.text() if not file_path: QMessageBox.critical(self, "错误", "请选择一个 Excel 文件") return if not table_name: QMessageBox.critical(self, "错误", "请输入目标数据库表名") return try: with POOL.connection() as conn: with conn.cursor() as cursor: df = pd.read_excel(file_path) if df.empty: QMessageBox.critical(self, "错误", "Excel 文件为空") return # 替换所有 NaN、inf、-inf 为 None df.replace([np.nan, np.inf, -np.inf], None, inplace=True) # 构建插入语句 if not table_name.isidentifier(): raise ValueError("表名包含非法字符") columns = ', '.join(df.columns.tolist()) placeholders = ', '.join(['%s'] * len(df.columns)) insert_sql = f"INSERT INTO `{table_name}` ({columns}) VALUES ({placeholders})" # 转换为元组列表 data = [tuple(row) for _, row in df.iterrows()] # 批量插入 cursor.executemany(insert_sql, data) conn.commit() QMessageBox.information(self, "成功", f"{cursor.rowcount} 条记录已成功导入到表 '{table_name}'") except Exception as e: try: conn.rollback() except: pass QMessageBox.critical(self, "错误", f"导入失败: {str(e)}") # 启动应用 if __name__ == "__main__": app = QApplication(sys.argv) window = ExcelImporter() window.show() sys.exit(app.exec_())
最新发布
07-27
<think>我们有一个需求:在创建Excel模板时,除了已有的列(如“姓名”、“产品名称”等),还需要自动生成“首拼”列,并且这个列的值需要根据前面的列(如“姓名”或“产品名称”)自动生成拼音首字母。 但是注意:在创建Excel模板的时候,我们只是创建了列标题,还没有数据。所以,我们不可能在创建模板时就生成首拼(因为还没有数据)。因此,我们需要在用户编辑完Excel并点击“我已编辑完成”之后,再对Excel文件进行处理,生成首拼列。 然而,我们的流程是:用户点击“创建Excel” -> 生成一个带有列标题的Excel文件(此时还没有首拼列) -> 用户打开Excel并编辑(比如输入了姓名) -> 用户点击“我已编辑完成” -> 此时我们需要自动为每一行生成首拼列。 但是,我们目前的设计中,用户点击“我已编辑完成”只是将文件路径设置到输入框,并没有做其他处理。所以我们需要修改流程:在用户点击“我已编辑完成”时,我们读取这个Excel文件,然后根据模板类型,自动计算首拼并添加到Excel文件中。 具体步骤: 1. 在创建模板时,我们创建的列标题中已经包含了“首拼”列(根据模板定义,如客户信息模板包含["姓名","首拼"])。所以,在用户编辑时,用户只需要填写“姓名”列,而“首拼”列是空的。 2. 当用户点击“我已编辑完成”时,我们读取这个Excel文件,然后对每一行,根据“姓名”列的值生成拼音首字母,并填入“首拼”列。 3. 然后保存Excel文件,并提示用户。 注意:我们还需要考虑用户可能已经填写了部分“首拼”列的情况,但我们的需求是自动生成,所以我们可以覆盖“首拼”列。 另外,我们需要一个生成拼音首字母的函数。我们可以使用第三方库pypinyin来实现。 首先,我们需要安装pypinyin(如果尚未安装): pip install pypinyin 然后,我们可以编写一个函数,将汉字转换为拼音首字母(大写)。 修改计划: 1. 在ExcelImporter类中增加一个方法:generate_sp,用于生成首拼。 2. 修改on_complete_clicked方法,在设置文件路径和表名之前,先读取Excel文件,生成首拼列,然后保存。 但是注意:我们的模板中,不同的模板对应的首拼依据列不同: - 客户信息:根据“姓名”列生成首拼 - 产品信息:根据“产品名称”列生成首拼 - 订单记录:根据“客户名”列生成首拼 因此,我们需要根据模板类型来决定使用哪一列生成首拼。 步骤: a. 在on_complete_clicked中,获取当前选择的模板名称(template_name)。 b. 根据模板名称,确定需要生成首拼的列(source_column)和首拼列(target_column='首拼')。 c. 读取Excel文件到DataFrame。 d. 检查DataFrame中是否包含source_column列,如果不包含,则报错。 e. 对source_column列中的每个值,生成首拼(大写字符串,对于非汉字字符,保留原字符,只取首字母?注意:我们只需要首字母,比如“张三”->'Z'和'S',然后连起来就是'ZS'。但注意,我们可能遇到多音字,不过首拼一般不考虑多音字,按常用读音即可)。 f. 将生成的字符串填入target_column列。 g. 保存DataFrame回Excel文件。 h. 然后设置文件路径和表名。 注意:如果用户已经填写了首拼列,我们也会覆盖。 生成首拼的函数示例(使用pypinyin): from pypinyin import pinyin, Style def get_sp(name): if not isinstance(name, str) or name.strip() == '': return '' # 将每个汉字转换为拼音首字母(大写),非汉字保留原字符(但只取第一个字符) # 注意:一个字符串可能包含多个汉字,我们将其全部转换为拼音首字母并连接 # 例如:张三 -> ZS # 对于非汉字,我们直接取第一个字符(因为整个字符串可能不是汉字,比如英文名) # 但是,我们也可以考虑:如果字符串是英文,那么取每个单词的首字母?但这里我们简单处理:只取整个字符串的首字母(因为我们的需求是首拼,通常就是第一个字的拼音首字母?) # 根据需求,我们可能需要整个字符串每个汉字的首字母连起来。但注意,我们的模板中,客户信息是姓名,可能是多个字。所以需要每个字都取首字母。 # 使用pinyin函数,指定风格为拼音首字母,并设置严格模式(遇到非汉字会原样输出) result = pinyin(name, style=Style.FIRST_LETTER, strict=False) # 将结果拼接成一个字符串(每个字的首字母) return ''.join([item[0] for item in result]).upper() 但是,我们也可以考虑另一种情况:如果用户输入的是英文,我们可能希望取每个单词的首字母。但根据我们的函数,对于非汉字,pinyin会原样返回,所以每个非汉字字符都会被当作一个字,然后取第一个字母(因为style=Style.FIRST_LETTER会返回每个字符的首字母?)。但实际上,对于非汉字,pinyin函数会返回这个字符本身(作为一个列表项)。例如:'abc' -> [['a'], ['b'], ['c']]? 但我们希望的是取整个字符串的首字母(即第一个字符)?还是每个单词的首字母?实际上,我们的需求是“首拼”,在中文环境下,通常是指每个汉字的拼音首字母连起来。对于英文,我们可能不需要拆分单词,直接取每个字符?但这样显然不对。 因此,我们需要明确: - 对于纯中文:按每个汉字取首字母。 - 对于包含英文的情况:我们可能希望整个字符串作为一个词,然后取每个字符?但这样英文名就会很长(比如Michael Jackson -> MJ,但我们希望是每个单词的首字母)。所以,我们可以改进:将字符串按空格分割成单词,然后对每个单词,如果是汉字则取每个汉字的首字母,如果是英文则取第一个字母(大写),然后连起来。 但这样处理比较复杂,而且我们的需求中,客户信息可能是中文名,也可能是英文名,但通常中文名没有空格。所以,我们可以这样: - 如果字符串中包含非英文字符(比如汉字),则按字符处理(每个字符取首字母)。 - 如果字符串是英文,则按空格分割,取每个单词的首字母。 但是,我们也可以统一处理:不管中英文,都按字符取首字母(这样英文名就会变成每个字符的首字母,比如“Michael”会变成“M”)。但这样显然不对,因为英文名我们通常取每个单词的首字母。 考虑到我们主要是中文应用,而且用户输入英文名的情况较少,我们可以先按照简单方式:只取每个字符的首字母(不管英文还是中文)。因为英文单词在输入时可能没有空格分隔,所以我们无法区分单词。 另一种做法:我们只处理汉字,非汉字字符原样保留(不转换),然后我们只取字符串的第一个字符(作为首拼)。但这样也不符合要求(因为要求是整个字符串每个汉字的首字母连起来)。 根据常见的需求,首拼通常是用于快速搜索,比如输入“zs”可以搜索“张三”。所以,我们需要的是整个字符串中每个汉字的首字母连起来。对于非汉字,我们可以忽略(或者只取第一个非汉字字符?)。但是,如果用户输入的是英文名,比如“John Smith”,我们希望首拼是“JS”,那么我们就需要按空格分割,然后取每个单词的首字母。 因此,我们修改需求:生成首拼的规则如下: 1. 将字符串按空格分割成单词。 2. 对每个单词: - 如果单词中包含汉字,则将该单词中所有汉字的首字母连起来(忽略非汉字字符)。 - 如果单词中不包含汉字,则取该单词的第一个字符(如果单词非空)。 3. 将所有单词的首字母部分连接起来,并转换为大写。 但是,这样处理比较复杂。为了简化,我们可以这样:将整个字符串(忽略空格)中的每个汉字取首字母,非汉字字符忽略?这样,对于“张三”得到“ZS”,对于“John Smith”得到“JS”吗?不行,因为非汉字字符被忽略了,所以“John Smith”会变成空(因为没有汉字)。 所以,我们需要区分中英文。我们可以这样: - 如果字符串中任意位置包含汉字,则整个字符串按字符处理,每个汉字取首字母,非汉字忽略(或者保留非汉字?但这样会混入非字母字符,不好)。 - 如果字符串中不包含汉字,则按空格分割,取每个单词的首字母(如果单词非空)。 但这样,对于中英文混合的情况,比如“张三John”,我们可能希望得到“ZSJ”还是“ZS”?按照需求,可能希望每个汉字和英文单词的首字母?但这样太复杂。 鉴于时间,我们采用一种简单通用的方式:只取每个汉字的首字母,忽略非汉字(包括空格和英文字母)。这样,对于“张三”,我们得到“ZS”;对于“John Smith”,我们得到空(因为没有汉字),这显然不行。 因此,我们调整:如果字符串中不包含汉字,则取每个单词(按空格分割)的首字母(大写),然后连接。如果包含汉字,则只取汉字的拼音首字母(忽略非汉字),然后连接。 实现步骤: - 首先判断字符串中是否包含汉字(可以使用正则表达式,或者遍历字符检查是否在汉字范围内)。 - 定义判断汉字的函数:一个字符是汉字如果其Unicode编码在\u4e00到\u9fa5之间。 具体函数实现如下: 但是,由于我们可能遇到中英文混合,我们也可以统一处理:将字符串中的每个字符,如果是汉字,则转换为拼音首字母,否则保留原字符(但如果是空格,则跳过,然后我们按空格分割?不行,因为这样我们就无法区分单词了)。所以,我们换一种思路: 步骤: 1. 将字符串按空格分割成单词。 2. 对每个单词: - 遍历单词中的每个字符,如果是汉字,则取其拼音首字母(大写),否则保留(但只保留字母,非字母字符忽略?)。 - 然后,我们只取这个单词的第一个非空字符(如果转换后这个单词有字符的话)?不对,我们要求的是整个字符串每个汉字的首字母连起来,所以一个单词中如果有多个汉字,我们就要多个首字母。 但是,这样和之前没有区别:一个单词中,汉字部分我们取每个汉字的首字母,非汉字部分我们保留,然后整个单词就变成了一个字符串(可能包含多个字母)。然后我们取这个字符串的首字母?不对,这样会很长。 重新思考:我们的首拼到底是什么意思? 在中文环境下,首拼通常是指每个汉字对应的拼音首字母。所以,对于“张三”,首拼是“ZS”;对于“李四”,首拼是“LS”。对于英文,我们可能希望取每个单词的首字母,比如“John Smith”的首拼是“JS”。对于中英文混合,比如“张三John”,我们可能希望取“ZSJ”(即:先取“张三”两个汉字的首字母,然后取英文单词“John”的首字母)。但是,这样处理规则复杂。 考虑到时间,我们简化处理:我们只处理汉字部分,非汉字部分全部忽略(包括空格和英文字母)。这样,对于“张三John”,我们只得到“ZS”。这样虽然简单,但英文名就无效了。 另一种常见做法:对于整个字符串,我们只取第一个字符(如果是汉字,则取拼音首字母;如果是英文字母,则直接取大写;如果是其他字符,则忽略)。这样得到的是首字母(单字母),而不是整个字符串每个汉字的首字母连起来。 我们需要明确需求:要求是“首拼”,在中文软件中通常指每个汉字拼音的首字母组成的字符串(用于快速检索)。所以,我们只处理汉字,忽略非汉字。这样,对于英文名,我们就无法生成首拼了。 但是,我们的模板中,客户信息可能是英文名,所以我们需要支持英文名。因此,我们采用以下规则: - 如果字符串中包含汉字,则只取所有汉字的首字母(按顺序),并连接起来(忽略非汉字)。 - 如果字符串中不包含汉字,则将字符串按空格分割成单词,然后取每个单词的首字母(如果单词非空,且首字符是字母,则取大写;如果不是字母,则跳过该单词),然后连接。 这样,对于“张三” -> "ZS" "John Smith" -> "JS" "张三 John" -> "ZS" (因为“John”是非汉字,所以被忽略;但注意,这里“张三”和“John”之间有空格,但空格分割后,“John”是非汉字单词,所以整个字符串包含汉字,所以只取汉字部分,忽略非汉字,因此得到“ZS”) 所以,我们需要一个函数,能够判断字符串中是否包含汉字。 实现如下: 由于我们可能用到pypinyin,并且需要判断汉字,我们可以这样: import re def contains_chinese(s): return re.search('[\u4e00-\u9fa5]', s) is not None def generate_sp(s): if not isinstance(s, str) or s.strip() == '': return '' if contains_chinese(s): # 只处理汉字部分,非汉字忽略 from pypinyin import pinyin, Style # 将字符串中的汉字提取出来,然后转换为拼音首字母 # 注意:pinyin函数对于非汉字会返回空列表?不会,strict=False时,非汉字会原样输出,但我们可以过滤掉非汉字字符 # 我们直接对整个字符串进行转换,然后过滤掉非汉字字符的拼音结果(因为非汉字字符我们不需要) # 但是pinyin函数会为每个字符生成拼音,包括非汉字。所以我们可以: # result = pinyin(s, style=Style.FIRST_LETTER, strict=False) # 然后,我们只取那些字符是汉字的部分?但pinyin函数返回的是每个字符的拼音列表(每个字符对应一个列表项,每个列表项是一个包含一个字符串的列表) # 所以,我们可以这样:遍历字符串中的每个字符,如果是汉字,则取其拼音首字母,否则跳过。 # 但是,使用pinyin函数处理整个字符串,然后我们只取汉字字符的拼音(因为非汉字字符我们不要) # 然而,pinyin函数不能直接过滤非汉字,所以我们可以: chinese_chars = re.findall('[\u4e00-\u9fa5]', s) result = pinyin(chinese_chars, style=Style.FIRST_LETTER) return ''.join([item[0] for item in result]).upper() else: # 按空格分割,然后取每个单词的首字母(只取字母,非字母单词跳过) words = s.split() initials = [] for word in words: if word: # 非空单词 first_char = word[0] if first_char.isalpha(): initials.append(first_char.upper()) return ''.join(initials) 但是,上面的函数对于包含汉字的字符串,我们只提取了汉字字符,然后转换为拼音首字母。对于非汉字部分,我们完全忽略。这样,对于“张三John”,我们只取“张”和“三”,得到“ZS”。 但是,用户可能希望“张三John”的首拼是“ZSJ”(即汉字部分取首字母,非汉字部分取每个单词的首字母)?这需要更复杂的规则。由于需求没有明确,我们按照上述规则实现。 我们决定采用上述规则。 步骤总结: 1. 在on_complete_clicked方法中,读取Excel文件。 2. 根据模板类型,确定源列(source_column)。 3. 对DataFrame的source_column的每一行,应用generate_sp函数,将结果填入“首拼”列(注意:如果“首拼”列不存在,则先创建,但我们的模板中已经包含了“首拼”列)。 4. 保存DataFrame。 注意:如果用户没有填写源列(比如姓名列为空),则generate_sp会返回空字符串,这样首拼列就是空。 代码修改: 首先,在文件顶部导入re和pypinyin(注意:pypinyin需要安装,如果未安装,我们需要捕获异常并提示用户)。 然后,定义两个辅助函数:contains_chinese和generate_sp。 但是,注意:pypinyin的导入可能会增加启动负担,所以我们可以在需要的时候再导入(比如在generate_sp函数内部导入pypinyin),因为如果用户不使用中文,可能不需要。 但是,我们这里在函数内部导入,以避免一开始就依赖pypinyin(如果用户没有安装,在点击“我已编辑完成”时才会报错)。 我们可以在on_complete_clicked中捕获导入错误并提示用户安装pypinyin。 修改on_complete_clicked: try: # 读取Excel文件 df = pd.read_excel(self.last_created_excel) # 获取模板类型 template_name = self.template_combo.currentText() # 根据模板类型确定源列 if template_name == "客户信息": source_col = "姓名" elif template_name == "产品信息": source_col = "产品名称" elif template_name == "订单记录": source_col = "客户名" else: # 默认情况下,我们不知道源列,所以跳过生成 QMessageBox.warning(self, "警告", f"未知模板类型,无法生成首拼") return # 检查源列是否存在 if source_col not in df.columns: QMessageBox.critical(self, "错误", f"Excel文件中缺少列: {source_col}") return # 生成首拼列 # 注意:我们的模板中已经有“首拼”列,所以这里我们直接覆盖 try: # 尝试导入pypinyin,如果失败则提示 from pypinyin import pinyin, Style except ImportError: QMessageBox.critical(self, "错误", "请安装pypinyin库: pip install pypinyin") return # 定义generate_sp函数(内部定义,避免污染类) import re def contains_chinese(s): return re.search('[\u4e00-\u9fa5]', s) is not None def generate_sp(s): if not isinstance(s, str) or s.strip() == '': return '' if contains_chinese(s): chinese_chars = re.findall('[\u4e00-\u9fa5]', s) if not chinese_chars: return '' result = pinyin(chinese_chars, style=Style.FIRST_LETTER) return ''.join([item[0] for item in result]).upper() else: words = s.split() initials = [] for word in words: if word: first_char = word[0] if first_char.isalpha(): initials.append(first_char.upper()) return ''.join(initials) # 应用generate_sp函数到源列 df['首拼'] = df[source_col].apply(generate_sp) # 保存回Excel文件 df.to_excel(self.last_created_excel, index=False) # 提示用户 QMessageBox.information(self, "完成", "首拼已自动生成并填充!") # 然后设置文件路径和表名 self.file_path_edit.setText(self.last_created_excel) self.table_name_edit.setText(template_name) self.complete_button.setEnabled(False) # 重置按钮状态 except Exception as e: QMessageBox.critical(self, "错误", f"生成首拼失败: {str(e)}") 但是,注意:我们在生成首拼的过程中,可能会遇到某个单元格的内容不是字符串(比如数字),所以我们需要在generate_sp函数中处理非字符串的情况。 修改generate_sp函数: def generate_sp(s): if not isinstance(s, str): # 如果是数字或其他类型,转换为字符串 s = str(s) if s.strip() == '': return '' ... # 其余相同 另外,我们还需要注意:在读取Excel时,可能有些单元格是NaN,我们需要先填充空字符串?但是,我们在读取时已经处理了NaN?不,我们在读取后,如果源列有NaN,那么apply的时候会传入NaN,然后我们转换为字符串'nan',这显然不对。所以,我们需要在apply之前,将源列中的NaN替换为空字符串。 修改: df[source_col] = df[source_col].fillna('') # 将NaN替换为空字符串 因此,在应用函数之前,先替换NaN。 完整步骤: df = pd.read_excel(self.last_created_excel) ... df[source_col] = df[source_col].fillna('') df['首拼'] = df[source_col].apply(generate_sp) 这样,空值会被转换为空字符串,然后generate_sp返回空字符串。 但是,注意:我们可能还有其他列,但这里我们只处理源列和首拼列。 修改后的on_complete_clicked方法: def on_complete_clicked(self): """处理编辑完成按钮点击事件""" if not self.last_created_excel or not os.path.exists(self.last_created_excel): QMessageBox.warning(self, "警告", "未找到创建的Excel文件") return try: # 读取Excel文件 df = pd.read_excel(self.last_created_excel) # 获取模板类型 template_name = self.template_combo.currentText() # 根据模板类型确定源列 if template_name == "客户信息": source_col = "姓名" elif template_name == "产品信息": source_col = "产品名称" elif template_name == "订单记录": source_col = "客户名" else: # 默认情况下,我们不知道源列,所以跳过生成 QMessageBox.warning(self, "警告", f"未知模板类型,无法生成首拼") return # 检查源列是否存在 if source_col not in df.columns: QMessageBox.critical(self, "错误", f"Excel文件中缺少列: {source_col}") return # 尝试导入pypinyin,如果失败则提示 try: from pypinyin import pinyin, Style except ImportError: QMessageBox.critical(self, "错误", "请安装pypinyin库: pip install pypinyin") return # 定义辅助函数 import re def contains_chinese(s): return re.search('[\u4e00-\u9fa5]', s) is not None def generate_sp(s): if not isinstance(s, str): s = str(s) if s.strip() == '': return '' if contains_chinese(s): chinese_chars = re.findall('[\u4e00-\u9fa5]', s) if not chinese_chars: return '' result = pinyin(chinese_chars, style=Style.FIRST_LETTER) return ''.join([item[0] for item in result]).upper() else: words = s.split() initials = [] for word in words: if word: first_char = word[0] if first_char.isalpha(): initials.append(first_char.upper()) return ''.join(initials) # 将源列中的NaN替换为空字符串 df[source_col] = df[source_col].fillna('') # 生成首拼列 df['首拼'] = df[source_col].apply(generate_sp) # 保存回Excel文件 df.to_excel(self.last_created_excel, index=False) QMessageBox.information(self, "完成", "首拼已自动生成并填充!") # 然后设置文件路径和表名 self.file_path_edit.setText(self.last_created_excel) self.table_name_edit.setText(template_name) self.complete_button.setEnabled(False) # 重置按钮状态 except Exception as e: QMessageBox.critical(self, "错误", f"生成首拼失败: {str(e)}") 但是,注意:我们保存时覆盖了原文件,并且没有备份。如果生成过程中出错,可能导致原文件损坏。因此,我们可以先保存到一个临时文件,然后替换原文件?或者,我们可以在内存中操作,最后再写回。 由于数据量不会很大,我们可以直接写回原文件,但是要确保在出错时不要覆盖原文件。所以,我们可以先写到一个临时文件,然后替换。 改进:使用一个临时文件,操作成功后再替换原文件。 import shutil try: # ... 生成df # 保存到临时文件 temp_file = tempfile.mktemp(suffix='.xlsx') df.to_excel(temp_file, index=False) # 替换原文件 shutil.move(temp_file, self.last_created_excel) except Exception as e: # 如果出错,删除临时文件(如果存在),并报错 if os.path.exists(temp_file): os.remove(temp_file) raise e 但是,这样会覆盖原文件,而用户可能已经编辑了其他内容,所以我们希望保留其他内容。由于我们只修改了“首拼”列,并且是覆盖,所以直接写回原文件也可以。但是,如果用户同时编辑了其他内容,我们写回时会不会丢失?不会,因为我们读取了整个文件,然后修改了首拼列,再写回。所以,用户的其他编辑也会保留。 但是,注意:我们使用pandas读取和写回,可能会丢失格式(比如字体、颜色等)。但是,我们的需求是快速生成首拼,而且用户刚刚编辑完成,还没有进行格式设置,所以可以接受。 因此,我们直接写回原文件。 最后,我们还需要考虑性能:如果Excel文件很大,生成首拼可能会比较慢。但一般情况下,一次导入的数据量不会太大。 现在,我们修改代码。 注意:由于我们使用了pypinyin,在第一次导入时可能会稍慢(因为要加载拼音库),但可以接受。 另外,我们还需要注意:在生成首拼时,如果某个单元格的字符串很长(包含大量汉字),re.findall可能会占用较多内存,但一般姓名不会很长。 修改后的完整代码:</think>我将为你的PyQt5应用添加自动生成首拼的功能。以下是完整的代码实现,包括在用户点击"我已编辑完成"时自动生成首拼列: ```python import sys import pandas as pd from 连接池到实验 import * from PyQt5.QtWidgets import ( QApplication, QWidget, QLabel, QLineEdit, QPushButton, QFileDialog, QMessageBox, QVBoxLayout, QComboBox, QHBoxLayout ) import numpy as np import os import tempfile import win32com.client as win32 # 用于操作Excel import pypinyin # 用于生成拼音首字母 # 主窗口类 class ExcelImporter(QWidget): def __init__(self): super().__init__() self.setWindowTitle("Excel 导入 MySQL 工具") self.resize(600, 400) # 增加窗口高度以容纳新控件 self.last_created_excel = None # 记录最后创建的Excel文件路径 self.templates = { "客户信息": ["姓名", "首拼"], "产品信息": ["产品名称", "首拼"], "订单记录": ["客户名", "首拼"] } self.init_ui() def init_ui(self): layout = QVBoxLayout() # 模板选择区域 layout.addWidget(QLabel("选择模板:")) template_layout = QHBoxLayout() self.template_combo = QComboBox() self.template_combo.addItems(self.templates.keys()) template_layout.addWidget(self.template_combo) self.create_button = QPushButton("创建Excel") self.create_button.clicked.connect(self.create_excel_template) template_layout.addWidget(self.create_button) self.complete_button = QPushButton("我已编辑完成") self.complete_button.clicked.connect(self.on_complete_clicked) self.complete_button.setEnabled(False) # 初始禁用 template_layout.addWidget(self.complete_button) layout.addLayout(template_layout) # 文件路径输入框 layout.addWidget(QLabel("选择 Excel 文件:")) self.file_path_edit = QLineEdit() self.browse_button = QPushButton("浏览") self.browse_button.clicked.connect(self.select_file) file_layout = QHBoxLayout() file_layout.addWidget(self.file_path_edit) file_layout.addWidget(self.browse_button) layout.addLayout(file_layout) # 表名输入框 layout.addWidget(QLabel("目标数据库表名:")) self.table_name_edit = QLineEdit() layout.addWidget(self.table_name_edit) # 导入按钮 self.import_button = QPushButton("导入数据库") self.import_button.clicked.connect(self.import_to_database) layout.addWidget(self.import_button) self.setLayout(layout) def create_excel_template(self): """创建Excel模板并打开""" try: # 获取选中的模板名称 template_name = self.template_combo.currentText() # 创建临时Excel文件 with tempfile.NamedTemporaryFile(suffix='.xlsx', delete=False) as tmp: file_path = tmp.name self.last_created_excel = file_path # 创建DataFrame并写入列标题 headers = self.templates[template_name] df = pd.DataFrame(columns=headers) df.to_excel(self.last_created_excel, index=False) # 使用Excel 2013打开文件 excel = win32.gencache.EnsureDispatch('Excel.Application') excel.Visible = True workbook = excel.Workbooks.Open(os.path.abspath(self.last_created_excel)) # 启用"我已编辑完成"按钮 self.complete_button.setEnabled(True) QMessageBox.information(self, "成功", f"Excel模板已创建,请编辑后点击'我已编辑完成'") except Exception as e: QMessageBox.critical(self, "错误", f"创建Excel失败: {str(e)}") def generate_sp(self, s): """生成拼音首字母""" if not isinstance(s, str) or not s.strip(): return "" # 处理中文字符串 if any('\u4e00' <= char <= '\u9fff' for char in s): # 获取拼音首字母 initials = pypinyin.lazy_pinyin(s, style=pypinyin.Style.FIRST_LETTER) return ''.join(initials).upper() else: # 处理非中文(英文)名称 words = s.split() if not words: return "" # 取每个单词的首字母 return ''.join(word[0].upper() for word in words if word) def on_complete_clicked(self): """处理编辑完成按钮点击事件,生成首拼列""" if not self.last_created_excel or not os.path.exists(self.last_created_excel): QMessageBox.warning(self, "警告", "未找到创建的Excel文件") return try: # 读取Excel文件 df = pd.read_excel(self.last_created_excel) # 获取模板类型 template_name = self.template_combo.currentText() # 确定源列名 if template_name == "客户信息": source_col = "姓名" elif template_name == "产品信息": source_col = "产品名称" elif template_name == "订单记录": source_col = "客户名" else: QMessageBox.warning(self, "警告", "未知模板类型") return # 检查源列是否存在 if source_col not in df.columns: QMessageBox.critical(self, "错误", f"Excel文件中缺少'{source_col}'列") return # 处理空值 df[source_col] = df[source_col].fillna('') # 生成首拼列 df['首拼'] = df[source_col].apply(self.generate_sp) # 保存回Excel文件 df.to_excel(self.last_created_excel, index=False) # 提示用户 QMessageBox.information(self, "成功", "首拼已自动生成并填充!") # 设置文件路径和表名 self.file_path_edit.setText(self.last_created_excel) self.table_name_edit.setText(template_name) self.complete_button.setEnabled(False) # 重置按钮状态 except Exception as e: QMessageBox.critical(self, "错误", f"生成首拼失败: {str(e)}") def select_file(self): file_path, _ = QFileDialog.getOpenFileName(self, "选择 Excel 文件", "", "Excel 文件 (*.xlsx *.xls)") if file_path: self.file_path_edit.setText(file_path) def import_to_database(self): file_path = self.file_path_edit.text() table_name = self.table_name_edit.text() if not file_path: QMessageBox.critical(self, "错误", "请选择一个 Excel 文件") return if not table_name: QMessageBox.critical(self, "错误", "请输入目标数据库表名") return try: with POOL.connection() as conn: with conn.cursor() as cursor: df = pd.read_excel(file_path) if df.empty: QMessageBox.critical(self, "错误", "Excel 文件为空") return # 替换所有 NaN、inf、-inf 为 None df.replace([np.nan, np.inf, -np.inf], None, inplace=True) # 构建插入语句 if not table_name.isidentifier(): raise ValueError("表名包含非法字符") columns = ', '.join(df.columns.tolist()) placeholders = ', '.join(['%s'] * len(df.columns)) insert_sql = f"INSERT INTO `{table_name}` ({columns}) VALUES ({placeholders})" # 转换为元组列表 data = [tuple(row) for _, row in df.iterrows()] # 批量插入 cursor.executemany(insert_sql, data) conn.commit() QMessageBox.information(self, "成功", f"{cursor.rowcount} 条记录已成功导入到表 '{table_name}'") except Exception as e: try: conn.rollback() except: pass QMessageBox.critical(self, "错误", f"导入失败: {str(e)}") # 启动应用 if __name__ == "__main__": app = QApplication(sys.argv) window = ExcelImporter() window.show() sys.exit(app.exec_()) ``` ### 代码解释: 1. **添加拼音生成功能**: - 引入了 `pypinyin` 库来处理中文拼音转换 - 新增了 `generate_sp()` 方法,用于生成拼音首字母: - 对于中文:使用 `pypinyin.lazy_pinyin()` 获取每个字符的首字母 - 对于英文:取每个单词的首字母并大写 - 处理空值和异常情况 2. **修改 on_complete_clicked 方法**: - 当用户点击"我已编辑完成"时,自动生成首拼列 - 根据模板类型确定源列(姓名、产品名称或客户名) - 读取Excel文件,处理空值 - 应用 `generate_sp()` 方法生成"首拼"列 - 保存修改后的Excel文件 - 显示成功消息并设置界面状态 3. **其他改进**: - 增加了更健壮的错误处理 - 优化了空值处理逻辑 - 添加了模板类型检查 ### 使用说明: 1. 用户选择模板并点击"创建Excel" 2. 编辑Excel文件(填写姓名、产品名称或客户名) 3. 点击"我已编辑完成"按钮 4. 程序自动生成"首拼"列并保存 5. 点击"导入数据库"完成数据导入 ### 注意事项: 1. 确保已安装所需库:`pip install pypinyin pandas pyqt5 pywin32` 2. 对于非中文名称,程序会取每个单词的首字母 3. 空值会被自动处理为空字符串
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值