""" 图片加载工具 用于从URL加载图片并转换为PIL/CTkImage格式 优化版本:使用连接池、线程池和缓存 """ import io import requests from PIL import Image from typing import Optional import customtkinter as ctk from concurrent.futures import ThreadPoolExecutor from functools import lru_cache import threading # ======================== 全局资源管理 ======================== # HTTP会话(连接池) _session = None _session_lock = threading.Lock() def get_session() -> requests.Session: """获取全局HTTP会话(单例模式)""" global _session if _session is None: with _session_lock: if _session is None: _session = requests.Session() # 配置连接池 adapter = requests.adapters.HTTPAdapter( pool_connections=10, # 连接池大小 pool_maxsize=20, # 最大连接数 max_retries=2, # 重试次数 pool_block=False ) _session.mount('http://', adapter) _session.mount('https://', adapter) return _session # 线程池(用于图片加载) _thread_pool = None _thread_pool_lock = threading.Lock() def get_thread_pool() -> ThreadPoolExecutor: """获取全局线程池(单例模式)""" global _thread_pool if _thread_pool is None: with _thread_pool_lock: if _thread_pool is None: # 限制最多同时加载10张图片 _thread_pool = ThreadPoolExecutor( max_workers=10, thread_name_prefix="ImageLoader" ) return _thread_pool # ======================== 图片加载函数 ======================== def load_image_from_url( url: str, size: tuple = (150, 150), timeout: int = 5 # 降低超时时间,避免长时间阻塞 ) -> Optional[ctk.CTkImage]: """ 从URL加载图片(使用连接池) Args: url: 图片URL size: 目标尺寸 (width, height) timeout: 超时时间(秒) Returns: CTkImage对象,失败返回None """ try: # 使用全局session(连接复用) session = get_session() # 下载图片 response = session.get(url, timeout=timeout) response.raise_for_status() # 转换为PIL Image image_data = io.BytesIO(response.content) pil_image = Image.open(image_data) # 转换为RGB(防止RGBA等格式问题) if pil_image.mode != 'RGB': pil_image = pil_image.convert('RGB') # 创建CTkImage ctk_image = ctk.CTkImage( light_image=pil_image, dark_image=pil_image, size=size ) return ctk_image except requests.exceptions.Timeout: print(f"加载图片超时: {url}") return None except requests.exceptions.RequestException as e: print(f"网络请求失败 {url}: {e}") return None except Exception as e: print(f"加载图片失败 {url}: {e}") return None @lru_cache(maxsize=100) def create_placeholder_image( size: tuple = (150, 150), text: str = "加载失败" ) -> ctk.CTkImage: """ 创建占位图片(带缓存) Args: size: 图片尺寸 text: 显示文字 Returns: CTkImage对象 """ # 创建灰色背景图片 pil_image = Image.new('RGB', size, color=(200, 200, 200)) ctk_image = ctk.CTkImage( light_image=pil_image, dark_image=pil_image, size=size ) return ctk_image def cleanup_resources(): """清理全局资源(程序退出时调用)""" global _session, _thread_pool # 关闭线程池 if _thread_pool is not None: _thread_pool.shutdown(wait=False) _thread_pool = None # 关闭HTTP会话 if _session is not None: _session.close() _session = None