You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Nuitka打包Tkinter生成的exe闪退无反馈问题求助

问题描述

直接运行Python脚本时程序完全正常,但使用Nuitka打包生成exe文件后,双击exe控制台会短暂打开后关闭,无任何反应;通过CMD运行exe,程序仅执行完毕却无输出。已尝试添加主循环,问题仍存在。

运行要求:用户桌面需存在名为“Project Fluvius”的文件夹,内含若干PDF文件和一个XLS表格。

代码示例
import os
import re
import pandas as pd
import PyPDF2
import getmac  # MAC地址校验用
import requests  # 获取在线时间用
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
from tkinter import Tk, simpledialog, messagebox
from datetime import datetime, timedelta
import sys  # 用于sys.exit(1)
import traceback  # 日志记录错误用

# 日志文件设置
log_file = open("error_log.txt", "w")
sys.stdout = log_file
sys.stderr = log_file

# 定义路径和全局变量
desktop_path = os.path.join(os.path.expanduser('~'), 'Desktop')
project_folder = os.path.join(desktop_path, 'Project XXXXXX')  # 注意:此处需改为"Project Fluvius"
pdf_folder_path = project_folder  # 假设PDF文件在"Project XXXXXX"文件夹内
master_file_path = os.path.join(project_folder, 'lege meetstaat.1.xls')
output_pdf_path = os.path.join(project_folder, 'pdf_totals_output.pdf')
timestamp_file = os.path.join(project_folder, 'timestamp.txt')  # 存储首次运行日期

TRIAL_PERIOD_DAYS = 90  # 90天试用期限
AUTHORIZED_MAC_ADDRESS = "XX:XX:XX:XX:XX:XX"  # 替换为授权MAC地址

# 函数定义
def get_mac_address():
    """获取当前设备的MAC地址"""
    mac_address = getmac.get_mac_address()
    print(f"检测到MAC地址: {mac_address}")
    return mac_address

def check_mac_address():
    """校验设备是否授权"""
    print("MAC地址校验")
    mac_address = get_mac_address()
    if mac_address != AUTHORIZED_MAC_ADDRESS:
        print(f"未授权设备,访问被拒绝。当前MAC地址: {mac_address}")
        messagebox.showerror("未授权访问", "该设备无权使用此应用。")
        sys.exit(1)

def get_online_time():
    """从在线API获取当前时间"""
    try:
        print("获取在线时间")
        response = requests.get('http://worldtimeapi.org/api/ip')
        if response.status_code == 200:
            online_time = response.json()['datetime']
            print(f"获取到在线时间: {online_time}")
            return datetime.strptime(online_time, "%Y-%m-%dT%H:%M:%S.%f%z").replace(tzinfo=None)
    except Exception as e:
        print(f"获取在线时间出错: {e}")
        return None  # 无法获取在线时间时返回None

def store_first_run_date(first_run_date):
    """将首次运行日期写入文件"""
    with open(timestamp_file, 'w') as f:
        f.write(first_run_date.strftime('%Y-%m-%d'))
    print(f"首次运行日期已存储: {first_run_date.strftime('%Y-%m-%d')}")

def load_first_run_date():
    """从文件加载首次运行日期"""
    if os.path.exists(timestamp_file):
        with open(timestamp_file, 'r') as f:
            date_str = f.read().strip()
            print(f"加载到首次运行日期: {date_str}")
            return datetime.strptime(date_str, '%Y-%m-%d')
    print("未找到首次运行日期记录。")
    return None

def check_trial_period():
    """校验试用期限是否过期"""
    print("试用期限校验")
    first_run_date = load_first_run_date()
    current_time = get_online_time() or datetime.now()

    if first_run_date is None:
        first_run_date = current_time
        store_first_run_date(first_run_date)
    else:
        days_since_first_run = (current_time - first_run_date).days
        print(f"距首次运行已过去: {days_since_first_run}天")

        if days_since_first_run > TRIAL_PERIOD_DAYS:
            root = Tk()
            root.withdraw()
            messagebox.showinfo("订阅过期", "您的订阅已过期,请续费以继续使用。")
            sys.exit(1)

def try_remove_output_pdf():
    """删除已存在的输出PDF文件"""
    print("检查是否存在已生成的PDF")
    if os.path.exists(output_pdf_path):
        try:
            os.remove(output_pdf_path)
            print(f"已删除旧PDF文件: {output_pdf_path}")
        except PermissionError:
            root = Tk()
            root.withdraw()
            messagebox.showerror("文件占用", f"请先关闭文件'{output_pdf_path}'再重试。")
            raise

def extract_text_from_pdf(pdf_path):
    """从PDF文件中提取文本"""
    print(f"正在提取PDF文本: {pdf_path}")
    text = ""
    try:
        with open(pdf_path, 'rb') as pdf_file:
            reader = PyPDF2.PdfReader(pdf_file)
            for page in range(len(reader.pages)):
                text += reader.pages[page].extract_text()
    except Exception as e:
        print(f"读取PDF出错: {e}")
    return text

def find_codes_in_pdf_text(pdf_text):
    """从PDF文本中匹配目标编码"""
    return re.findall(r'\b3\d{6}\b|\b3\d{8}\b', pdf_text)

def calculate_total_from_codes(codes, master_data, coefficient):
    """根据匹配到的编码计算总金额"""
    print("正在计算总金额")
    total = 0.0
    fixed_coefficient_codes = ['3003597', '3003869', '3003868']

    for code in codes:
        code = str(code.strip())
        if code in master_data['Code'].values:
            price = master_data.loc[master_data['Code'] == code, 'Price'].values[0]
            if code in fixed_coefficient_codes:
                total += price
            else:
                total += price * coefficient
    return total

def get_calculation_coefficient():
    """获取用户输入的计算系数"""
    root = Tk()
    root.withdraw()
    coefficient = simpledialog.askfloat("输入", "请输入计算系数:")
    return coefficient if coefficient else 1  # 未输入时默认系数为1

def generate_pdf(pdf_totals, output_pdf_path):
    """生成汇总PDF报告"""
    print("正在生成PDF报告")
    c = canvas.Canvas(output_pdf_path, pagesize=A4)
    width, height = A4
    
    c.setFont("Helvetica-Bold", 16)
    c.drawCentredString(width / 2, height - 50, "Fluvius totaal meetstaat")
    c.line(width / 2 - 100, height - 60, width / 2 + 100, height - 60)
    c.setFont("Helvetica", 12)

    line_height = 15
    margin_left = 40
    margin_right = width - 40
    total_sum = 0.0
    y_position = height - 100

    for pdf_file, total in pdf_totals.items():
        pdf_title = os.path.splitext(pdf_file)[0]
        total_sum += total
        c.drawString(margin_left, y_position, pdf_title)
        c.drawRightString(margin_right, y_position, f"€{total:.2f}")
        y_position -= line_height

    c.setFont("Helvetica-Bold", 12)
    c.drawCentredString(width / 2, y_position - 20, f"Totaal van meetstaten: €{total_sum:.2f}")
    c.showPage()
    c.save()

# 主函数
def main():
    try:
        # 打印执行节点用于调试
        print("脚本开始执行")
        
        # 加载主数据表格
        master_data = pd.read_excel(master_file_path, sheet_name='Grondw')
        master_data = master_data.iloc[:, [1, 25]]
        master_data.columns = ['Code', 'Price']
        master_data['Code'] = master_data['Code'].astype(str).str.replace(r'\.0$', '', regex=True).str.strip()

        check_mac_address()
        check_trial_period()
        try_remove_output_pdf()

        pdf_files = [f for f in os.listdir(pdf_folder_path) if f.endswith('.pdf') and f != os.path.basename(output_pdf_path)]
        pdf_totals = {}
        coefficient = get_calculation_coefficient()

        for pdf_file in pdf_files:
            pdf_path = os.path.join(pdf_folder_path, pdf_file)
            pdf_text = extract_text_from_pdf(pdf_path)
            codes = find_codes_in_pdf_text(pdf_text)
            total = calculate_total_from_codes(codes, master_data, coefficient)
            pdf_totals[pdf_file] = total

        generate_pdf(pdf_totals, output_pdf_path)
        print(f"PDF报告已生成: {output_pdf_path}")

    except Exception as e:
        traceback.print_exc(file=log_file)
        sys.exit(1)
    
    finally:
        log_file.close()

# 脚本入口
if __name__ == "__main__":
    main()
    main_window.mainloop()  # 错误:main_window未定义
解决方案

1. 修复未定义变量错误

删除代码最后一行的main_window.mainloop(),代码中从未创建main_window对象,这行代码会触发NameError,导致程序打包后直接崩溃退出。

2. 修正项目文件夹路径

将代码中project_folder定义里的"Project XXXXXX"改为"Project Fluvius",否则程序会找不到指定的文件夹,导致后续文件加载失败:

project_folder = os.path.join(desktop_path, 'Project Fluvius')

3. 调整日志输出路径

将日志文件生成到项目文件夹内,避免因当前工作目录不确定导致日志丢失:

# 替换原日志初始化代码
log_file_path = os.path.join(project_folder, "error_log.txt")
log_file = open(log_file_path, "w")
sys.stdout = log_file
sys.stderr = log_file

4. 优化异常提示

在异常处理中添加弹窗提示,让用户直观知道程序出错,并引导查看日志:

except Exception as e:
    traceback.print_exc(file=log_file)
    # 添加弹窗提示
    root = Tk()
    root.withdraw()
    messagebox.showerror("运行错误", f"程序执行出错,请查看项目文件夹中的error_log.txt获取详情:\n{str(e)}")
    sys.exit(1)

5. 正确使用Nuitka打包命令

打包时需确保依赖库和资源被正确包含,使用以下命令(替换your_script.py为实际脚本名):

nuitka --standalone --onefile --enable-plugin=tk-inter --include-data-dir="C:\Windows\Fonts"=./fonts your_script.py
  • --standalone:生成独立运行环境
  • --onefile:生成单个exe文件(可选,若启动慢可去掉此参数生成文件夹)
  • --enable-plugin=tk-inter:确保Tkinter组件被正确打包
  • --include-data-dir:包含系统字体,解决reportlab的Helvetica字体依赖问题

内容的提问来源于stack exchange,提问作者Hitokiri

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 11:35:55