从外部Python模块调用函数耗时15秒,求助排查解决
COM端口扫描模块化后性能骤降的排查与解决
问题描述
为实现代码模块化,将COM端口扫描、选择函数迁移至独立模块Snippets\COMSelector.py,替代原主程序内的代码复制逻辑。主程序内直接定义这些函数时,执行瞬间完成;但通过from Snippets import COMSelector调用COMScanner()和targetSelector()时,单次执行耗时长达15秒(测试仅连接1个COM接口,排除人工操作影响)。
核心代码
COMSelector.py模块代码
from serial.tools import list_ports from serial import Serial, SerialException import tkinter as tk from functools import partial def COMScanner(): allports = [] ports = list_ports.comports() for port, desc, hwid in sorted(ports): hwid = hwid.split(" SER")[0] # print("{}: {} [{}]".format(port, desc, hwid)) #DBG try: s = Serial(port) s.close() state = "FREE" except (OSError, SerialException): state = "BUSY" pass allports.append([port, hwid, state]) return allports def targetSelector(COMS): global sel_com sel_com = None # 检测到多个COM端口时,用户需选择目标端口 if COMS and len(COMS) > 1: COMSelector = tk.Tk() baseWidth = 8 * len("".join(max(COMS, key=len))) baseHeight = 35 * len(COMS) finalWidth = baseWidth + 10 finalHeight = baseHeight # * len(COMS) screen_width = COMSelector.winfo_screenwidth() screen_height = COMSelector.winfo_screenheight() x_coordinate = int((screen_width / 2) - (finalWidth / 2)) y_coordinate = int((screen_height / 2) - (finalHeight / 2)) COMSelector.geometry("{}x{}+{}+{}".format(finalWidth, finalHeight, x_coordinate, y_coordinate)) COMSelector.update_idletasks() COMSelector.title("Select Target") def save_action(selection): global sel_com, user_pass sel_com = str(selection) COMSelector.destroy() for id, COM in enumerate(COMS): COM_HANDLE = str(COM[0]) COM_PID = str(COM[1]) COM_STATE = str(COM[2]) action = partial(save_action, COM_HANDLE) COMLabel = tk.Label(master=COMSelector, text=COM_PID, borderwidth=0.5, relief="solid") COMButton = tk.Button(master=COMSelector, text=("[" + COM_STATE + "] " + COM_HANDLE), command=action ) if COM_STATE == "FREE": COMButton.config(state="active") else: COMButton.config(state="disabled") COMButton.grid(row=id, column=0, pady=5, padx=5, sticky="WE") COMLabel.grid(row=id, column=1, pady=5, padx=5, sticky="WE") COMSelector.mainloop() else: sel_com = COMS[0] try: return sel_com except: return "No target selected."
主程序调用代码
from Snippets import COMSelector from datetime import datetime # 补全缺失的导入 [...] start_time = datetime.now() available_COMS = COMSelector.COMScanner() target = COMSelector.targetSelector(available_COMS) print(datetime.now() - start_time)
问题排查与解决方案
1. 解决Serial连接无超时阻塞问题
COMScanner中创建Serial(port)时未指定超时参数,默认是无限等待,若遇到未响应的端口会导致长时间阻塞。这是最可能的性能瓶颈。
优化代码:
给Serial实例添加超时限制,比如timeout=1:
try: s = Serial(port, timeout=1) # 限制连接尝试时间为1秒 s.close() state = "FREE" except (OSError, SerialException): state = "BUSY"
2. 优化端口枚举速度
list_ports.comports()默认会枚举所有类型的端口,包括虚拟端口,可能导致枚举延迟。可以排除虚拟端口加快速度:
ports = list_ports.comports(include_links=False)
3. 移除全局变量避免上下文冲突
targetSelector中使用的全局变量sel_com在模块上下文可能存在意外状态,改用nonlocal替代全局变量:
def targetSelector(COMS): sel_com = None if COMS and len(COMS) > 1: COMSelector = tk.Tk() # ... 界面代码保持不变 ... def save_action(selection): nonlocal sel_com sel_com = str(selection) COMSelector.destroy() # ... 界面代码保持不变 ... COMSelector.mainloop() else: sel_com = COMS[0] if COMS else None return sel_com if sel_com else "No target selected."
4. 验证延迟来源
单独测试COMScanner()的耗时,定位是扫描还是选择函数导致的延迟;在COMScanner()中添加打印语句,输出每个端口的处理时间,排查具体阻塞的端口。
内容的提问来源于stack exchange,提问作者Terabyte
相关产品推荐
相关产品推荐

