错误处理与异常管理是构建健壮 SunvStation 脚本的核心能力。通过系统化的错误处理机制,您的脚本能够优雅地处理异常情况、记录关键错误信息、确保数据完整性,并在用户界面中提供清晰的反馈。本页面将深入解析 sunvpy 库的错误处理架构、实用模式和最佳实践。 ## 错误处理架构概览 sunvpy 库采用分层设计,通过 **LogMixin** 混入类提供统一的错误记录能力,结合多种防御性编程模式形成完整的错误处理体系。这种架构将错误记录、异常捕获和状态验证分离到不同层次,确保代码的可维护性和扩展性。 ```mermaid classDiagram class Logger { <> +debug(msg) +info(msg) +warning(msg) +error(msg) +critical(msg) } class LogAware { <<抽象基类>> +set_logger(Logger)* +log_error_msg(str)* } class LogMixin { +Logger logger +set_logger(Logger) +log_error_msg(str) } class SSProcessManager { +Logger logger +log_error_msg(str) } class ErrorPatterns { <<错误处理模式>> +null_check_return +type_conversion_try +silent_exception_pass +early_return_validation } LogAware <|-- LogMixin LogMixin <|-- SSProcessManager SSProcessManager ..> Logger : 使用 SSProcessManager ..> ErrorPatterns : 应用 ``` 这种架构设计的核心优势在于**关注点分离**:业务逻辑代码通过统一的 `log_error_msg()` 接口记录错误,而日志的具体输出配置由您完全掌控。错误处理模式则分布在各个 Mixin 类中,针对不同场景提供灵活的应对策略。 Sources: [log_mixin.py](ssprocess_mixins/log_mixin.py#L19-L42), [PySSProcess.py](PySSProcess.py#L34-L35) ## 错误处理机制层次 sunvpy 的错误处理分为四个层次,每个层次应对不同类型的错误场景。理解这个层次结构对于编写健壮的脚本至关重要。 ```mermaid flowchart LR A[错误输入] --> B[第一层:参数验证
空值检查/范围检查] B --> C{是否通过?} C -->|否| D[返回默认值/空值
记录警告日志] C -->|是| E[第二层:类型转换
try-except 捕获] E --> F{转换成功?} F -->|否| G[使用容错值/返回 False
记录错误日志] F -->|是| H[第三层:业务操作
空指针检查] H --> I{操作成功?} I -->|否| J[返回空结果/失败标志
记录错误日志] I -->|是| K[第四层:资源清理
try-finally 确保] K --> L[正常返回结果] style D fill:#fff4e1 style G fill:#ffe1e1 style J fill:#ffe1e1 style L fill:#e1ffe1 ``` ### 第一层:参数验证与空值检查 这是错误处理的第一道防线,通过前置条件检查防止无效输入进入核心逻辑。sunvpy 广泛使用**边界检查**和**空值检查**模式。 **空值检查返回默认值模式:** ```python def getSysSelGeoList(self): """获取当前选中的几何对象列表。""" map = self.getCurrentMap() if map is None: return None # 空值检查,返回 None 表示失败 return GeoBaseList(map.m_selGeoList) ``` Sources: [selection_mixin.py](ssprocess_mixins/selection_mixin.py#L133-L140) **边界检查返回空字符串模式:** ```python def getSelGeoValue(self, index: int, fieldName: str) -> str: """获取当前选择集中特定几何对象的属性值。""" if index < 0 or index >= len(self.selGeoList): return "" # 边界检查,返回空字符串 geo = self.selGeoList[index] if geo is None: return "" # 空值检查 # ... 后续逻辑 ``` Sources: [selection_mixin.py](ssprocess_mixins/selection_mixin.py#L156-L165) ### 第二层:类型转换异常捕获 当涉及字符串到数值的转换时,sunvpy 使用 try-except 块捕获 `ValueError` 异常,并提供容错机制或失败标记。 **类型转换容错模式:** ```python def setSelGeoValue(self, index: int, fieldName: str, value: str): """设置当前选择集中特定几何对象的属性值。""" # ... 前置检查代码 if fieldName.lower() == "ssobj_code": # 类型转换容错:若不能转为 int 则直接用原值 try: code_value = int(value) except Exception: code_value = value # 容错处理 for geo in geoList: geo.setCode(code_value) ``` Sources: [selection_mixin.py](ssprocess_mixins/selection_mixin.py#L368-L376) **类型转换失败返回 False 模式:** ```python case "ssobj_angle": try: angle = float(value) geo.setAngle(angle) except ValueError: return False # 转换失败,返回 False 标记 ``` Sources: [geo_edit_mixin.py](ssprocess_mixins/geo_edit_mixin.py#L313-L317) | 错误类型 | 异常类 | 处理策略 | 使用场景 | |---------|--------|---------|---------| | 数值转换失败 | `ValueError` | 返回 False 或使用容错值 | 角度、比例、时间等数值属性设置 | | 参数类型错误 | `TypeError` | 返回 False 或跳过操作 | 几何对象属性赋值 | | 通用异常 | `Exception` | 使用容错值或静默处理 | 关键操作的容错处理 | ### 第三层:业务操作错误处理 在执行地理空间操作时,可能遇到数据源不可用、数据集不存在等运行时错误。sunvpy 通过**错误日志记录**和**提前返回**模式处理这类错误。 **错误日志记录模式:** ```python def createNewObjByCode(self, code: int) -> tuple: """根据指定的代码创建一个新的几何对象。""" self.curNewObj = (None, None) # 重置当前新建对象 ds_eps = self.map.getCurrentDataSourceEPS() if ds_eps is None: self.log_error_msg("无法获取当前数据源!") # 记录错误 return self.curNewObj # 返回空元组 fea = ds_eps.getFeature(code) if fea is None: fea = ds_eps.getFeature(0) dataset = getDatasetByFeaCode(ds_eps, fea) if dataset is None: self.log_error_msg(f"无法获取数据集, code:{code}") # 记录错误 return self.curNewObj # 返回空元组 # ... 后续逻辑 ``` Sources: [geo_edit_mixin.py](ssprocess_mixins/geo_edit_mixin.py#L167-L215) **错误日志与循环继续模式:** ```python def persistTempGeoList(self, geoVec: GeoBaseVector): """将临时几何对象列表持久化到地图中。""" for i in range(geoVec.size()): geo: GeoBase = geoVec.at(i) code = geo.getCode() fea = ds_eps.getFeature(code) if fea is None: fea = ds_eps.getFeature(0) dataset = getDatasetByFeaCode(ds_eps, fea) if dataset is None: self.log_error_msg(f"无法获取数据集, code:{code}") continue # 跳过当前对象,继续处理下一个 obj = GeoObject(dataset, generateUniqueId()) obj.setGeoBase(geo) persistVec.push_back(geo, obj) return persistVec ``` Sources: [geo_edit_mixin.py](ssprocess_mixins/geo_edit_mixin.py#L595-L615) ### 第四层:静默异常处理 对于非关键操作(如进度条关闭、UI 清理),sunvpy 使用静默异常处理模式,确保即使操作失败也不会影响主流程。 **静默异常处理模式:** ```python def disable_progress(self): """关闭进度条显示并立即关闭正在显示的进度条。""" self.enable_progress = False if self.progress is not None: try: closeProgress() # 尝试关闭进度条 except Exception: pass # 静默处理,不中断流程 self.progress = None ``` Sources: [progress_mixin.py](ssprocess_mixins/progress_mixin.py#L46-L53) | 处理模式 | 适用场景 | 优点 | 缺点 | |---------|---------|------|------| | **异常抛出** | 关键错误、不可恢复状态 | 强制调用方处理,问题可见 | 需要额外 try-except 代码 | | **日志记录** | 可恢复错误、业务逻辑错误 | 不中断流程,便于追踪 | 需要配置日志系统 | | **返回失败值** | 可选操作、容错场景 | 调用方灵活处理 | 需要检查返回值 | | **静默处理** | 非关键清理、UI 操作 | 简洁,不影响主流程 | 错误信息丢失 | ## 错误日志记录机制 sunvpy 通过 `LogMixin` 提供统一的错误日志记录接口,支持将自定义配置的 Logger 注入到 SSProcess 中。这种设计允许您灵活控制日志的输出方式、格式和存储位置。 ### 日志记录接口 `LogMixin` 提供两个核心方法用于日志记录: | 方法 | 签名 | 功能 | 使用场景 | |------|------|------|---------| | `set_logger()` | `set_logger(logger: Logger)` | 注入自定义日志记录器 | 脚本初始化时配置 | | `log_error_msg()` | `log_error_msg(error_msg: str)` | 记录错误消息 | 发生错误时调用 | **LogMixin 实现细节:** ```python class LogMixin(LogAware): """Mixin class for logging operations""" logger : Optional[Logger] = None def set_logger(self, logger: Logger): self.logger = logger def log_error_msg(self, error_msg: str): print("error log in!") if self.logger is not None: self.logger.error(error_msg) # 调用标准库 Logger 的 error 方法 ``` Sources: [log_mixin.py](ssprocess_mixins/log_mixin.py#L32-L42) ### 日志配置流程 配置错误日志记录需要创建 Logger 实例、设置处理器和格式化器,最后注入到 SSProcess 中。 ```mermaid flowchart TD A[开始配置日志] --> B[创建 Logger 实例
logging.getLogger'script_name'] B --> C[创建 FileHandler
输出到文件] C --> D[创建 StreamHandler
输出到控制台] D --> E[设置 Formatter
定义日志格式] E --> F[配置日志级别
DEBUG/INFO/WARNING/ERROR] F --> G[添加 Handler 到 Logger] G --> H[调用 SSProcess.set_logger'logger'] H --> I[日志配置完成] I --> J{调用 log_error_msg} J -->|logger 未配置| K[仅打印到控制台] J -->|logger 已配置| L[按配置输出到文件/控制台] style H fill:#e1f5ff style L fill:#e1ffe1 ``` **完整日志配置示例:** ```python import logging from sunvpy import SSProcess # 步骤 1:创建 Logger 实例 logger = logging.getLogger('my_script') logger.setLevel(logging.DEBUG) # 步骤 2:创建文件处理器 file_handler = logging.FileHandler('script_log.log', mode='a', encoding='utf-8') file_handler.setLevel(logging.ERROR) # 只记录 ERROR 及以上级别 # 步骤 3:创建控制台处理器 console_handler = logging.StreamHandler() console_handler.setLevel(logging.WARNING) # 只显示 WARNING 及以上级别 # 步骤 4:设置格式化器 formatter = logging.Formatter( fmt='%(asctime)s [%(filename)s:%(lineno)d] %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) file_handler.setFormatter(formatter) console_handler.setFormatter(formatter) # 步骤 5:将处理器添加到 Logger logger.addHandler(file_handler) logger.addHandler(console_handler) # 步骤 6:注入到 SSProcess SSProcess.set_logger(logger) # 步骤 7:使用示例 SSProcess.createNewObjByCode(3103013) # 如果出错,会记录到日志 ``` Sources: [log_mixin.py](ssprocess_mixins/log_mixin.py#L26-L42) ### 日志级别选择指南 根据错误的严重程度和影响范围,合理选择日志级别对于问题诊断至关重要。 | 日志级别 | 错误类型 | 示例场景 | 处理策略 | |---------|---------|---------|---------| | DEBUG | 调试信息 | 对象创建过程、参数传递细节 | 仅开发阶段启用 | | INFO | 重要操作 | 脚本开始/结束、批量处理完成 | 记录关键执行节点 | | WARNING | 潜在问题 | 使用默认值、对象不存在但可继续 | 不影响脚本运行 | | ERROR | 错误但可恢复 | 数据源不可用、数据集获取失败 | 记录详细错误信息 | | CRITICAL | 严重错误 | 无法恢复的异常、致命失败 | 建议终止脚本执行 | **最佳实践:为关键操作添加错误日志记录** ```python def batch_export_data(dataset_name: str): """批量导出数据示例""" # 关键操作前记录日志 SSProcess.logger.info(f"开始批量导出数据集: {dataset_name}") try: dataset = SSProcess.map.getDataset(dataset_name) if dataset is None: SSProcess.logger.error(f"数据集不存在: {dataset_name}") return False SSProcess.startProgress("批量导出", 1000) for i in range(1000): # ... 导出逻辑 SSProcess.stepProgress(f"处理第 {i} 条记录") SSProcess.logger.info(f"批量导出完成,共处理 1000 条记录") SSProcess.closeProgress() return True except Exception as e: SSProcess.logger.critical(f"批量导出发生严重错误: {str(e)}") SSProcess.closeProgress() return False ``` Sources: [geo_edit_mixin.py](ssprocess_mixins/geo_edit_mixin.py#L590-L615) ## 常见错误场景与处理策略 了解 sunvpy 中的常见错误场景及其标准处理模式,能够帮助您编写更加健壮的脚本。以下总结了几类典型错误及其推荐处理策略。 ### 场景一:数据源和数据集访问错误 访问地理空间数据时,经常遇到数据源不可用或数据集不存在的情况。 | 错误场景 | 检测方法 | 推荐处理策略 | |---------|---------|-------------| | 数据源不可用 | `getCurrentDataSourceEPS()` 返回 None | 记录错误日志,返回空结果 | | 数据集不存在 | `getDataset()` 返回 None | 记录错误日志,跳过当前对象 | | 特征代码无效 | `getFeature(code)` 返回 None | 使用默认特征代码 0 | **标准处理模式:** ```python def createGeoObjectByCode(self, code: int): """根据编码创建地理对象的标准错误处理模式""" # 获取数据源 ds_eps = self.map.getCurrentDataSourceEPS() if ds_eps is None: self.log_error_msg("无法获取当前数据源!") return (None, None) # 返回空元组 # 获取特征 fea = ds_eps.getFeature(code) if fea is None: fea = ds_eps.getFeature(0) # 使用默认特征 if fea is None: self.log_error_msg(f"无法获取特征,code: {code}") return (None, None) # 获取数据集 dataset = getDatasetByFeaCode(ds_eps, fea) if dataset is None: self.log_error_msg(f"无法获取数据集,code: {code}") return (None, None) # 创建对象 obj_type = fea.nObjectType obj, geo = self.createDefaultGeoBase(obj_type, dataset) if obj is None or geo is None: self.log_error_msg(f"创建地物失败,code: {code}") return (None, None) return (obj, geo) ``` Sources: [geo_edit_mixin.py](ssprocess_mixins/geo_edit_mixin.py#L167-L225) ### 场景二:属性类型转换错误 设置对象属性时,需要将字符串值转换为正确的数据类型(int、float、日期等),可能遇到格式错误。 | 属性类型 | 目标类型 | 转换错误处理 | |---------|---------|-------------| | 角度、比例 | float | 捕获 ValueError,返回 False | | 编码、线型 | int | 捕获 Exception,使用原值容错 | | 创建/修改时间 | DateTime | 捕获 ValueError,返回 False | | 坐标值 | float | 捕获 ValueError,返回 False | **类型转换错误处理模式:** ```python def setPropertyWithValidation(self, geo: GeoBase, field_name: str, value: str) -> bool: """带类型转换验证的属性设置方法""" field_name = field_name.lower().strip() match field_name: case "ssobj_angle" | "ssobj_scalex" | "ssobj_scaley": try: float_value = float(value) geo.setAngle(float_value) if field_name == "ssobj_angle" else None # ... 其他赋值逻辑 return True except ValueError: self.log_error_msg(f"数值转换失败: {field_name} = {value}") return False case "ssobj_createtime" | "ssobj_modifytime": try: datetime_value = parseDateTime(value) geo.setCreateTime(datetime_value) if field_name == "ssobj_createtime" else None return True except ValueError: self.log_error_msg(f"日期转换失败: {field_name} = {value}") return False case "ssobj_code": try: int_value = int(value) geo.setCode(int_value) return True except Exception: # 容错处理:保持原值 self.log_error_msg(f"编码转换失败,保持原值: {value}") return True case _: # 其他属性直接设置 geo.setObjName(value) return True ``` Sources: [geo_edit_mixin.py](ssprocess_mixins/geo_edit_mixin.py#L310-L390) ### 场景三:索引越界与空对象访问 在遍历选择集或访问集合元素时,可能遇到索引越界或空对象引用错误。 | 错误场景 | 检测条件 | 推荐处理 | |---------|---------|---------| | 索引越界 | `index < 0` 或 `index >= len(list)` | 返回空值,记录警告 | | 空对象引用 | `obj is None` | 跳过处理,返回 False | | 列表为空 | `list.empty() is True` | 直接返回,不执行操作 | **集合访问安全模式:** ```python def safeGetGeoValue(self, index: int, field_name: str) -> str: """安全的属性值获取方法""" # 第一层检查:索引边界 if index < 0 or index >= len(self.selGeoList): self.log_error_msg(f"索引越界: {index}, 选择集大小: {len(self.selGeoList)}") return "" # 第二层检查:对象是否为 None geo = self.selGeoList[index] if geo is None: self.log_error_msg(f"索引 {index} 处的对象为空") return "" # 第三层检查:属性是否存在 if field_name.startswith('['): # 扩展属性 fields, values = StringArray(), StringArray() self.map.getExtentAttr(geo, fields, values) idx = self.findIndexInStringArray(field_name[1:-1], fields) if idx == -1: return "" # 属性不存在,返回空字符串 return values[idx] else: # 基本属性 return self.objBaseAttr.getGeoValue(self.map, geo, field_name) ``` Sources: [selection_mixin.py](ssprocess_mixins/selection_mixin.py#L156-L190) ### 场景四:资源清理错误 在进度条关闭、图形释放等资源清理操作中,可能遇到异常,但不应中断主流程。 | 操作类型 | 异常原因 | 推荐处理 | |---------|---------|---------| | 关闭进度条 | UI 已关闭、句柄无效 | try-except 静默处理 | | 释放图形 | 图形已被释放 | 检查对象状态后操作 | | 清除选择集 | 选择集为空 | 检查状态后再清理 | **资源清理模式:** ```python def cleanupResources(self): """安全的资源清理方法""" # 安全关闭进度条 if self.progress is not None: try: closeProgress() except Exception: pass # 静默处理,不中断流程 finally: self.progress = None # 安全释放图形 for geo in self.selGeoList: if geo is not None: try: geo.freeGraphics() except Exception: pass # 静默处理 # 安全清除选择集 try: self.selGeoList.clear() self.selNoteList.clear() except Exception: pass # 清理失败不影响其他操作 ``` Sources: [progress_mixin.py](ssprocess_mixins/progress_mixin.py#L46-L53) ## 错误处理最佳实践 基于 sunvpy 库的设计理念和实际应用经验,以下错误处理最佳实践将帮助您构建更加健壮和可维护的脚本。 ### 实践一:前置条件检查 在任何操作开始前进行参数和状态验证,遵循"**先检查,后执行**"的原则。 ```python def updateGeoAttributes(self, index: int, attributes: dict) -> bool: """更新地物属性的最佳实践示例""" # 前置条件 1:检查选择集不为空 if len(self.selGeoList) == 0: self.log_error_msg("选择集为空,无法更新属性") return False # 前置条件 2:检查索引有效性 if index < 0 or index >= len(self.selGeoList): self.log_error_msg(f"索引无效: {index}") return False # 前置条件 3:检查参数不为空 if not attributes: self.log_error_msg("属性字典为空") return False # 前置条件 4:检查地图可用 if self.map is None: self.log_error_msg("地图实例不可用") return False # 通过所有验证后,执行核心逻辑 geo = self.selGeoList[index] success_count = 0 for field, value in attributes.items(): if self.setSelGeoValue(index, field, str(value)): success_count += 1 else: self.log_error_msg(f"设置属性失败: {field} = {value}") return success_count > 0 ``` ### 实践二:精确的异常捕获 避免使用过于宽泛的 `except Exception`,优先捕获具体的异常类型,提高错误定位的准确性。 | 推荐做法 | 不推荐做法 | 原因 | |---------|-----------|------| | `except ValueError` | `except Exception` | 捕获精确的异常类型 | | `except (ValueError, TypeError)` | `except` | 明确捕获多种预期异常 | | 先捕获具体异常,最后捕获 Exception | 只捕获 Exception | 分层处理不同错误 | **精确异常捕获示例:** ```python def convertAngleValue(self, value: str) -> Optional[float]: """角度值转换的精确异常处理""" try: return float(value) except ValueError: self.log_error_msg(f"角度值格式错误: {value}") return None except TypeError: self.log_error_msg(f"角度值类型错误,应为字符串: {type(value)}") return None except Exception as e: self.log_error_msg(f"角度值转换发生未预期错误: {str(e)}") return None ``` ### 实践三:错误恢复与回滚 对于涉及多个步骤的操作,实现错误恢复机制,确保失败时能够回滚到一致状态。 ```python def batchUpdateWithRollback(self, indices: list, field_name: str, value: str) -> bool: """带回滚机制的批量更新""" # 保存原始值 original_values = [] valid_indices = [] for idx in indices: if 0 <= idx < len(self.selGeoList): original_value = self.getSelGeoValue(idx, field_name) original_values.append(original_value) valid_indices.append(idx) if not valid_indices: self.log_error_msg("没有有效的索引需要更新") return False # 执行更新 updated_count = 0 for i, idx in enumerate(valid_indices): if self.setSelGeoValue(idx, field_name, value): updated_count += 1 else: # 更新失败,执行回滚 self.log_error_msg(f"索引 {idx} 更新失败,开始回滚...") for j, rollback_idx in enumerate(valid_indices[:i]): self.setSelGeoValue(rollback_idx, field_name, original_values[j]) return False self.logger.info(f"批量更新成功: {updated_count} 个对象") return True ``` ### 实践四:日志与用户反馈结合 将技术性的错误日志与面向用户的友好反馈结合,提升用户体验。 ```python def importDataWithFeedback(self, file_path: str) -> bool: """带用户反馈的数据导入""" try: # 技术日志 self.logger.info(f"开始导入文件: {file_path}") # 用户反馈 SSProcess.startProgress("数据导入", 100) SSProcess.stepProgress("正在读取文件...") # 导入逻辑 # ... 执行导入操作 SSProcess.stepProgress("导入完成") SSProcess.closeProgress() # 成功日志 self.logger.info(f"文件导入成功: {file_path}") return True except FileNotFoundError: # 技术日志 self.logger.error(f"文件不存在: {file_path}") # 用户反馈(通过进度条标题) SSProcess.startProgress("导入失败", 1) SSProcess.stepProgress(f"错误:文件不存在 - {file_path}") SSProcess.closeProgress() return False except PermissionError: self.logger.error(f"文件访问权限不足: {file_path}") SSProcess.startProgress("导入失败", 1) SSProcess.stepProgress("错误:无文件访问权限") SSProcess.closeProgress() return False except Exception as e: self.logger.critical(f"导入发生严重错误: {str(e)}") SSProcess.startProgress("导入失败", 1) SSProcess.stepProgress("错误:未知错误,请查看日志") SSProcess.closeProgress() return False ``` ### 实践五:错误码与状态管理 为不同的错误场景定义清晰的错误码,便于调用方根据错误类型采取不同的恢复策略。 ```python class ScriptError: """脚本错误码定义""" SUCCESS = 0 INVALID_PARAM = 1 DATA_SOURCE_ERROR = 2 DATASET_ERROR = 3 CONVERSION_ERROR = 4 INDEX_OUT_OF_RANGE = 5 OBJECT_NULL = 6 def createObjectWithErrorCode(self, code: int) -> tuple[int, tuple]: """带错误码的对象创建方法""" # 检查数据源 ds_eps = self.map.getCurrentDataSourceEPS() if ds_eps is None: self.log_error_msg("数据源不可用") return (ScriptError.DATA_SOURCE_ERROR, (None, None)) # 检查特征 fea = ds_eps.getFeature(code) if fea is None: fea = ds_eps.getFeature(0) if fea is None: self.log_error_msg(f"特征代码无效: {code}") return (ScriptError.DATASET_ERROR, (None, None)) # 检查数据集 dataset = getDatasetByFeaCode(ds_eps, fea) if dataset is None: self.log_error_msg(f"数据集不存在,code: {code}") return (ScriptError.DATASET_ERROR, (None, None)) # 创建对象 obj_type = fea.nObjectType obj, geo = self.createDefaultGeoBase(obj_type, dataset) if obj is None or geo is None: self.log_error_msg(f"对象创建失败,code: {code}") return (ScriptError.DATASET_ERROR, (None, None)) return (ScriptError.SUCCESS, (obj, geo)) # 使用示例 error_code, (obj, geo) = SSProcess.createObjectWithErrorCode(3103013) if error_code == ScriptError.SUCCESS: print("对象创建成功") elif error_code == ScriptError.DATA_SOURCE_ERROR: print("请检查数据源配置") elif error_code == ScriptError.DATASET_ERROR: print("请检查数据集和特征代码") ``` ## 错误调试与问题排查 当脚本遇到错误时,系统化的调试流程能够快速定位问题根源。本节提供一套完整的错误调试方法论。 ### 错误调试流程 ```mermaid flowchart TD A[脚本发生错误] --> B{是否已配置日志?} B -->|否| C[配置日志记录器
参考[配置日志记录器] 37-pei-zhi-ri-zhi-ji-lu-qi] B -->|是| D[检查日志文件
查找 ERROR/CRITICAL 级别消息] C --> D D --> E{错误类型判断} E -->|数据源/数据集错误| F[检查数据源连接
验证数据集名称] E -->|类型转换错误| G[检查输入值格式
验证数据类型] E -->|索引越界错误| H[检查选择集大小
验证索引范围] E -->|空对象引用错误| I[检查对象初始化
验证对象状态] E -->|未知错误| J[启用 DEBUG 级别日志
添加详细打印信息] F --> K[修复问题] G --> K H --> K I --> K J --> L[分析堆栈跟踪
定位代码位置] L --> K K --> M[重新运行脚本
验证修复效果] M --> N{问题解决?} N -->|是| O[调试完成] N -->|否| P[重复调试流程] style C fill:#fff4e1 style K fill:#e1ffe1 style O fill:#e1ffe1 ``` ### 常见错误排查清单 | 错误现象 | 可能原因 | 排查步骤 | 相关页面 | |---------|---------|---------|---------| | "无法获取当前数据源" | 工作空间未初始化或数据源关闭 | 检查 [工作空间与地图概念](8-gong-zuo-kong-jian-yu-di-tu-gai-nian) | - | | "无法获取数据集" | 图层名称错误或图层不存在 | 验证图层名称,使用 [获取工作空间信息](34-huo-qu-gong-zuo-kong-jian-xin-xi) | - | | "索引越界" | 选择集为空或索引超出范围 | 使用 [获取选择集对象数量](14-bian-li-xuan-ze-ji-dui-xiang) 检查 | - | | "角度/比例转换失败" | 输入值格式不正确 | 验证输入值,确保为数字字符串 | - | | "对象创建失败" | 特征代码无效或对象类型不支持 | 参考 [地理对象类型定义](42-di-li-dui-xiang-lei-xing-ding-yi) | - | ### 调试辅助代码 以下辅助函数可以帮助您在调试时获取详细的系统状态信息。 ```python def printSystemStatus(): """打印系统状态信息,用于调试""" print("=" * 60) print("SunvStation 系统状态") print("=" * 60) # 工作空间状态 workspace = SSProcess.getWorkspace() if workspace: print(f"✓ 工作空间已初始化") map = workspace.getCurrentScaleMap() if map: print(f"✓ 当前地图已加载") print(f" - 图层数量: {map.getLayerSize()}") print(f" - 总对象数: {map.getObjectSize(e_Null_Obj)}") else: print(f"✗ 当前地图未加载") else: print(f"✗ 工作空间未初始化") # 数据源状态 if SSProcess.map: ds_eps = SSProcess.map.getCurrentDataSourceEPS() if ds_eps: print(f"✓ 数据源已连接") else: print(f"✗ 数据源未连接") # 选择集状态 geo_count = SSProcess.getSelGeoCount() note_count = SSProcess.getSelNoteCount() print(f"✓ 选择集状态: {geo_count} 个地物, {note_count} 个注记") # 日志状态 if SSProcess.logger: print(f"✓ 日志记录器已配置: {SSProcess.logger.name}") print(f" - 日志级别: {logging.getLevelName(SSProcess.logger.level)}") print(f" - 处理器数量: {len(SSProcess.logger.handlers)}") else: print(f"✗ 日志记录器未配置") # 进度条状态 if SSProcess.progress: print(f"✓ 进度条已启动") else: print(f" 进度条未启动") print("=" * 60) # 使用示例 printSystemStatus() ``` ## 错误处理与相关功能的集成 错误处理机制与 sunvpy 的其他核心功能紧密协作,形成完整的脚本开发生态。理解这些集成关系有助于您构建更加全面的解决方案。 ### 错误处理与进度条管理 进度条操作与错误处理密切相关,特别是在长时间运行的任务中。当发生错误时,需要正确关闭进度条,避免 UI 卡顿。 ```python def batchProcessWithErrorHandling(self, total_count: int) -> bool: """批量操作中的错误处理与进度条协作""" try: SSProcess.startProgress("批量处理", total_count) for i in range(total_count): try: # 执行单次处理 result = self.processSingleItem(i) if result: SSProcess.stepProgress(f"处理完成: {i+1}/{total_count}") else: self.log_error_msg(f"项目处理失败: {i}") # 继续处理下一个项目,不中断整个流程 except Exception as e: self.log_error_msg(f"项目处理发生异常: {i}, 错误: {str(e)}") # 继续处理下一个项目 SSProcess.closeProgress() return True except Exception as e: # 外层异常,确保进度条关闭 self.logger.critical(f"批量操作严重错误: {str(e)}") try: SSProcess.closeProgress() except Exception: pass # 静默处理关闭错误 return False ``` Sources: [progress_mixin.py](ssprocess_mixins/progress_mixin.py#L32-L80) ### 错误处理与对象缓存机制 对象缓存操作中的错误需要特别小心,避免导致数据不一致。当缓存操作失败时,需要清理相关状态。 ```python def saveBufferWithValidation(self) -> bool: """带验证的对象缓存保存""" try: # 验证缓存不为空 if (self.bufferObjList.empty() and self.bufferNoteList.empty() and len(self.newBufferObjList) == 0 and len(self.newBufferNoteList) == 0): self.logger.info("缓存为空,无需保存") return True # 保存新建对象 all_new_objs = self.newBufferObjList + self.newBufferNoteList if all_new_objs: for obj, geo in all_new_objs: try: if geo is None: self.log_error_msg("新建对象为空,跳过保存") continue dataset = geo.getDatasetName() if not dataset: self.log_error_msg("对象数据集名称为空,跳过保存") continue # ... 保存逻辑 except Exception as e: self.log_error_msg(f"保存对象失败: {str(e)}") continue # 保存修改对象 if not self.bufferObjList.empty(): self.transMemoDataToExtendAttr(self.bufferObjList) self.map.saveDatabase(self.bufferObjList) if not self.bufferNoteList.empty(): self.transMemoDataToExtendAttr(self.bufferNoteList) self.map.saveDatabase(self.bufferNoteList) # 清理缓存(无论成功或失败) self.newBufferObjList.clear() self.newBufferNoteList.clear() self.bufferObjList.clear() self.bufferNoteList.clear() self.logger.info("缓存保存成功") return True except Exception as e: self.logger.critical(f"缓存保存严重错误: {str(e)}") # 清理缓存以避免不一致状态 self.newBufferObjList.clear() self.newBufferNoteList.clear() return False ``` Sources: [geo_edit_mixin.py](ssprocess_mixins/geo_edit_mixin.py#L475-L540) ### 错误处理与选择集管理 选择集操作中的错误处理需要特别注意,避免破坏选择集的状态一致性。 ```python def safeSelectAndProcess(self, condition_field: str, condition_op: str, condition_value: str) -> bool: """安全的选择和操作流程""" try: # 清理现有选择集 SSProcess.clearSelection() SSProcess.clearSelectCondition() # 设置选择条件 SSProcess.setSelectCondition(condition_field, condition_op, condition_value) # 执行过滤 SSProcess.selectFilter() # 检查选择结果 geo_count = SSProcess.getSelGeoCount() note_count = SSProcess.getSelNoteCount() total_count = geo_count + note_count if total_count == 0: self.logger.warning(f"没有找到符合条件的对象: {condition_field} {condition_op} {condition_value}") return True # 没有对象也是正常情况 self.logger.info(f"选择集创建成功: {geo_count} 个地物, {note_count} 个注记") # 处理选择集 success_count = 0 for i in range(total_count): try: # 处理单个对象 if i < geo_count: result = self.processGeo(i) else: result = self.processNote(i - geo_count) if result: success_count += 1 except Exception as e: self.log_error_msg(f"处理对象 {i} 失败: {str(e)}") continue self.logger.info(f"选择集处理完成: {success_count}/{total_count} 成功") return success_count > 0 except Exception as e: self.logger.critical(f"选择集操作严重错误: {str(e)}") # 清理选择集 try: SSProcess.clearSelection() SSProcess.clearSelectCondition() except Exception: pass return False ``` Sources: [selection_mixin.py](ssprocess_mixins/selection_mixin.py#L85-L110) ## 下一步学习建议 掌握错误处理与异常管理后,建议按照以下路径深入学习相关主题,构建完整的 SunvStation 脚本开发知识体系: - [配置日志记录器](37-pei-zhi-ri-zhi-ji-lu-qi) — 深入了解日志系统的详细配置方法和高级用法 - [进度条使用指南](38-jin-du-tiao-shi-yong-zhi-nan) — 学习进度条在长时间任务中的应用和错误处理 - [批量保存到数据库](24-pi-liang-bao-cun-dao-shu-ju-ku) — 了解批量操作中的错误处理和数据完整性保护 - [对象缓存机制](23-dui-xiang-huan-cun-ji-zhi) — 理解缓存操作中的状态管理和错误恢复策略 - [撤销标记管理](25-che-xiao-biao-ji-guan-li) — 学习撤销功能与错误处理的协作机制