Python脚本Try-Except在IDE正常运行,PyInstaller编译为exe后失效
问题:PyInstaller打包后try-except块未执行
我开发了一款基于Tkinter GUI的Python工具,其中一段try-except代码存在异常捕获问题。在VSCode中直接运行脚本时,这段代码能正常捕获文件写入异常并执行处理逻辑;但通过PyInstaller编译为exe后,该代码完全不执行。
有问题的代码片段
try: pldf_csv[data['sls_data']['add_columns']].write_csv(endpath,sep='\t') except: write_eror_status = True print("CANNOT WRITE FILE")
工具完整代码
IngestGenerator类
class IngestGenerator: def __init__(self, fn,fd,n3pl): self.filename = fn self.filedir = fd self.name3pl = n3pl def generate_result_csv(self): """To extend 3PL please refer to comment with <(extend this)> note Don't forget to extend the yaml when extending 3PL """ start_time = time.time() # with open("columns 1a.yaml", 'r') as f: with open(os.path.join(os.path.dirname(__file__), 'columns 1a.yaml'), 'r') as f: data = yaml.load(f, Loader=SafeLoader) with tempfile.TemporaryDirectory() as tmpdirname: try : # create new list if there's new 3pl behavior (extend this) list_type_like = [data['behavior']['type_like'][i]['name3pl'] for i in range(0,len(data['behavior']['type_like']))] #collects names of 3pl which have categorical column to be divided based on write_eror_status = False status = False for i in range(0,len(data['behavior']['type_like'])): if data['behavior']['type_like'][i]['name3pl']==self.name3pl: #get the name of category column and the values of categories (extend this whole if-statement) list_types = data['behavior']['type_like'][i]['cats'] cat_column = data['behavior']['type_like'][i]['categorical_col'] status = True else : pass if status == False: #for logging to check if its in the list of type-like 3pl (extend this whole if else statement) print("3PL cannot be found on type-like 3PL list") else: print("3PL can be found on type-like 3PL list") try: for cat in list_types: #dynamic list creation for each category (extend this line only) globals()[f"{cat}_final_df"] = [] except : print("3pl isn't split based on it's categories") xl = win32com.client.Dispatch("Excel.Application") print("Cast to CSV first (win32com)") wb = xl.Workbooks.Open(self.filename,ReadOnly=1) xl.DisplayAlerts = False xl.Visible = False xl.ScreenUpdating = False xl.EnableEvents = False sheet_names = [sheet.Name for sheet in wb.Sheets if sheet.Name.lower() != 'summary'] print("Sheet names") print(sheet_names) for sheet_name in sheet_names: print("Reading sheet "+sheet_name) ws = wb.Worksheets(sheet_name) ws.SaveAs(tmpdirname+"\\myfile_tmp_{}.csv".format(sheet_name), 24) used_columns = data['sls_data'][f'{self.name3pl.lower()}_used_columns'] renamed_columns = data['sls_data'][f'{self.name3pl.lower()}_rename_columns'] rowskip = data['behavior']['row_skip'][f'{self.name3pl.lower()}'] list_dtypes = [str for u in used_columns] print("CP 1") scandf = pl.scan_csv(tmpdirname+"\\myfile_tmp_{}.csv".format(sheet_name),skip_rows= rowskip,n_rows=10) #scan csv to get column name print(scandf.columns) scanned_cols = scandf.columns.copy() used_cols_inDF = [] #collects column names dynamically for i in range(0,len(used_columns)): if type(used_columns[i]) is list: #check for each scanned-columns which contained in yaml used_columns, append if the scanned columns exist in yaml for sc in scanned_cols: for uc in used_columns[i]: if sc == uc: print(f"Column match : {uc}") used_cols_inDF.append(uc) else:pass else: for sc in scanned_cols: #purpose is same with the if statement if sc == used_columns[i]: print(f"Column match : {used_columns[i]}") used_cols_inDF.append(used_columns[i]) else:pass print(used_cols_inDF) """ JNT files have everchanging column names. Some files only have Total Ongkir, some only have Total, and some might have Total and Total Ongkir. If both exists then will use column Total Ongkir (extend this if necessary i.e for special cases of 3pl) """ if self.name3pl == 'JNT': if "Total" in used_cols_inDF and "Total Ongkir" in used_cols_inDF: used_cols_inDF.remove("Total") else:pass else:pass pldf_csv = pl.read_csv(tmpdirname+"\\myfile_tmp_{}.csv".format(sheet_name), columns = used_cols_inDF, new_columns = renamed_columns, dtypes = list_dtypes, skip_rows= rowskip ).filter(~pl.fold(acc=True, f=lambda acc, s: acc & s.is_null(), exprs=pl.all(),)) #filter rows with all null values print(pldf_csv) print(pldf_csv.columns) for v in data['sls_data']['add_columns']: #create dynamic columns if "3pl invoice distance (m) (optional)" in v.lower() or "3pl cod amount (optional)" in v.lower(): pldf_csv = pldf_csv.with_column(pl.Series(name="{}".format(v),values= np.zeros(shape=pldf_csv.shape[0]))) elif "3pl tn (mandatory)" in v.lower() or "weight (kg) (optional)" in v.lower(): pass elif "total fee (3pl) (optional)" in v.lower(): pldf_csv = pldf_csv.with_column(pl.col(v).str.replace_all(",","").str.strip().cast(pl.Float64,False).fill_null(0)) else : pldf_csv = pldf_csv.with_column(pl.lit(None).alias(v)) print(pldf_csv) endpath = self.filedir+"\\{}_{}_{}.csv".format(get_file_name(file_name_appear_label["text"]),sheet_name,"IngestResult").replace('/','\\') if self.name3pl not in list_type_like: #(extend this line only) if self.name3pl == 'JNT': #(extend this line and its statement if necessary i.e for special cases of 3pl) pldf_csv = pldf_csv.with_column((pl.col("Total Fee (3PL) (Optional)")+pl.col("Biaya Asuransi").str.replace_all(",","").str.strip().cast(pl.Float64,False).fill_null(0)).alias("Total Fee (3PL) (Optional)")) else: pass print(pldf_csv) try: pldf_csv[data['sls_data']['add_columns']].write_csv(endpath,sep='\t') except: write_eror_status = True print("CANNOT WRITE FILE") elif self.name3pl in list_type_like: #(extend this line only) for cat in list_types: globals()[f"{cat}_final_df"].append(pldf_csv.filter(pl.col(cat_column).str.contains(cat))) if self.name3pl not in list_type_like: pass elif self.name3pl in list_type_like: for cat in list_types: globals()[f"{cat}_final_df"] = pl.concat(globals()[f"{cat}_final_df"]) print(globals()[f"{cat}_final_df"]) globals()[f"endpath_{cat}"] = self.filedir+"\\{}_{}_{}.csv".format(get_file_name(file_name_appear_label["text"]),cat,"IngestResult").replace('/','\\') print("done creating paths") try: globals()[f"{cat}_final_df"][data['sls_data']['add_columns']].write_csv(globals()[f"endpath_{cat}"],sep='\t') except : write_eror_status = True print("CANNOT WRITE FILE") if write_eror_status == False: progress_label["text"] = "Successful!" else: progress_label["text"] = "Cannot write result into Excel, please close related Excel files and kill Excel processes from Task Manager" print("Process finished") except Exception as e: print("ERROR with message") print(e) progress_label["text"] = "Failed due to {}".format(e) finally : wb.Close(False) submit_btn["state"] = "normal" browse_btn["state"] = "normal" print("Total exec time : {}".format((time.time()-start_time)/60))
按钮点击函数
def file_submit_btn_click(): if (file_name_appear_label["text"]==""): progress_label["text"] = "Please input your file" elif option_menu.get() == '': progress_label["text"] = "Please select 3pl name" else: try : submit_btn["state"] = "disabled" browse_btn["state"] = "disabled" progress_label["text"] = "Loading . . ." name3pl = option_menu.get() ingest = IngestGenerator(file_name,file_path,name3pl) print(get_file_name(file_name_appear_label["text"])) threading.Thread(target=ingest.generate_result_csv).start() except Exception as e: print(e)
解决思路及方案
1. 替换globals()的动态变量存储方式
PyInstaller打包后,globals()的作用域行为和脚本运行时不一致,动态创建的变量无法被正确访问,导致文件写入前的DataFrame获取失败,错误被外层大try-except捕获,内层try-except根本没机会执行。
修改方案:
用类实例字典替代globals()存储动态DataFrame:
# 在__init__中初始化字典 def __init__(self, fn,fd,n3pl): self.filename = fn self.filedir = fd self.name3pl = n3pl self.category_dfs = {} # 存储不同类别的DataFrame # 初始化空列表时 try: for cat in list_types: self.category_dfs[cat] = [] except : print("3pl isn't split based on it's categories") # 添加数据时 for cat in list_types: self.category_dfs[cat].append(pldf_csv.filter(pl.col(cat_column).str.contains(cat))) # 合并及写入时 for cat in list_types: self.category_dfs[cat] = pl.concat(self.category_dfs[cat]) print(self.category_dfs[cat]) endpath_cat = self.filedir+"\\{}_{}_{}.csv".format(get_file_name(file_name_appear_label["text"]),cat,"IngestResult").replace('/','\\') print("done creating paths") try: self.category_dfs[cat][data['sls_data']['add_columns']].write_csv(endpath_cat,sep='\t') except (IOError, PermissionError, pl.PolarsError) as e: write_eror_status = True print(f"CANNOT WRITE FILE: {str(e)}")
2. 明确捕获特定异常,避免裸except
裸except会捕获所有类型异常,但外层的大try-except会提前拦截写入前的错误(比如列不存在、DataFrame未初始化),导致内层try-except无法触发。
修改方案:
内层try-except只捕获文件写入相关的异常:
try: pldf_csv[data['sls_data']['add_columns']].write_csv(endpath,sep='\t') except (IOError, PermissionError, pl.PolarsError) as e: write_eror_status = True print(f"CANNOT WRITE FILE: {str(e)}")
同时在外层except中打印完整错误栈,定位问题根源:
except Exception as e: import traceback traceback.print_exc() # 打印完整错误栈 print(f"ERROR with message: {str(e)}") progress_label["text"] = f"Failed due to {str(e)}"
3. 修复PyInstaller打包的资源路径问题
exe运行时,os.path.dirname(__file__)的路径和脚本运行时不同,可能导致yaml文件加载失败,后续流程出错。
修改方案:
添加资源路径处理函数,并在打包时包含yaml文件:
import sys def resource_path(relative_path): """获取打包后的资源文件路径""" try: base_path = sys._MEIPASS # PyInstaller打包后的临时路径 except Exception: base_path = os.path.dirname(__file__) return os.path.join(base_path, relative_path) # 加载yaml文件时使用 with open(resource_path('columns 1a.yaml'), 'r') as f: data = yaml.load(f, Loader=SafeLoader)
打包时用--add-data参数包含yaml文件:
pyinstaller --add-data "columns 1a.yaml;." your_script.py
4. 确保线程内异常被正确处理
子线程中的异常如果未被捕获,不会主动传递到主线程,可能导致错误无法被感知。确保generate_result_csv内的所有分支都有异常处理逻辑。
内容的提问来源于stack exchange,提问作者random student
相关产品推荐
相关产品推荐

