大家好我是 SiKi老师。关卡工具读出生点坐标代码里一句x, y, z pos跑到一半抛出not enough values to unpack却看不出是哪一条记录出了问题。先别急着在外面套try我会先在拆包之前检查长度和类型让报错直接带上记录编号、实际分量个数和原始值。本文在 Linux 上的 Python 3.12.15 中运行另用 3.13.5 复跑同一脚本下面引用的输出两边一致。坐标样例全部自拟只在内存中检查不读写真实关卡文件也没有测试任何引擎的向量类型或场景导入。讨论的是工具端的数据协议不是引擎规定。官方在线文档标注的小版本是 3.12.15与本机一致。一 先复现拆包报错缺了哪些信息用几个自拟坐标直接拆包forposin[(1.0,2.0),(1,2,3,4),None]:try:x,y,zposexcept(ValueError,TypeError)asexc:print(type(exc).__name__,exc)本地输出三行ValueError not enough values to unpack (expected 3, got 2)、ValueError too many values to unpack (expected 3)、TypeError cannot unpack non-iterable NoneType object。这些信息说明了“期望 3 个”少的时候还给出实际个数但不会告诉你是哪个出生点、原始值长什么样。多出分量时这两个版本的消息里也没有实际个数。另外缺值是ValueError整条记录是None时却是TypeError只捕获一种异常会漏掉另一种。官方语言参考在赋值语句一节写明目标列表不带星号时右侧对象必须是可迭代对象并且元素个数与目标个数相同然后从左到右依次赋值。所以拆包本身只关心“能不能迭代、个数对不对”并不检查每个分量是不是数字。二 有些坏数据拆包不会报错更麻烦的是个数刚好是 3 的错误数据会直接“通过”x,y,z123print(x,y,z)x,y,z{x:1,y:2,z:3}print(x,y,z)本地输出1 2 3和x y z。第一行看起来像数字实际是三个字符1、2、3第二行拿到的是字典的三个键名不是坐标值。这类数据要到后面做加减时才报类型错误或者更糟被当成文本悄悄写进下一步。还有一个容易忽略的细节右侧是迭代器时拆包失败也会消耗元素。ititer([1,2,3,4,5])try:a,b,citexceptValueErrorasexc:print(exc,list(it))本地输出too many values to unpack (expected 3) [5]也就是失败后迭代器只剩最后一个元素。如果工具一边读流一边拆包出错后不要假设还能从原位置重试先把一条记录取成列表或元组再检查会稳妥得多。三 拆包前先检查长度、类型和数值我的做法是写一个小函数先检查再返回三元组报错消息里带上记录编号importmathfromcollections.abcimportSequencedefcheck_vec3(value,where):ifisinstance(value,(str,bytes))ornotisinstance(value,Sequence):raiseValueError(f{where}: 需要长度为3的列表或元组实际是{type(value).__name__})iflen(value)!3:raiseValueError(f{where}: 需要3个分量实际{len(value)}个:{value!r})out[]foraxis,iteminzip(xyz,value):iftype(item)notin(int,float):raiseValueError(f{where}.{axis}: 需要数字实际是{type(item).__name__})ifnotmath.isfinite(item):raiseValueError(f{where}.{axis}: 不是有限数{item!r})out.append(float(item))returntuple(out)x,y,zcheck_vec3([4,0,2],spawn_02)print(x,y,z)本地输出4.0 0.0 2.0。这里有几处是刻意的选择字符串和字节虽然也是序列但先排除避免123混进来字典不是Sequence会被拒绝分量用type(item) in (int, float)判断这样True不会被当成1math.isfinite拦住nan和无穷大。整数分量统一转成浮点是本文自拟协议的约定你的工具如果要求坐标必须是整数格子应改成对应规则。检查通过后再拆包拆包这一步就不会再出现个数错误出错时也只会出现在检查函数里消息格式统一。四 一次报告所有坏记录关卡表里可能不止一条坏数据。遇到第一条就抛异常修一条跑一次效率很低。我会把检查放在循环里收集所有错误后一起输出rows[(spawn_01,[0.0,1.5,-3.0]),(spawn_03,[1.0,2.0]),(spawn_04,[1.0,2.0,3.0,4.0]),(spawn_05,123),(spawn_07,[1.0,True,2.0]),(spawn_09,[float(nan),0.0,0.0]),(spawn_10,None),]good,errors{},[]forrow_id,valueinrows:try:good[row_id]check_vec3(value,row_id)exceptValueErrorasexc:errors.append(str(exc))print(good)forlineinerrors:print(line)本地输出第一行是{spawn_01: (0.0, 1.5, -3.0)}随后六行错误依次为spawn_03: 需要3个分量实际2个: [1.0, 2.0] spawn_04: 需要3个分量实际4个: [1.0, 2.0, 3.0, 4.0] spawn_05: 需要长度为3的列表或元组实际是 str spawn_07.y: 需要数字实际是 bool spawn_09.x: 不是有限数 nan spawn_10: 需要长度为3的列表或元组实际是 NoneType每条都能直接定位到记录和分量。是否在有错误时整批拒绝还是只跳过坏行属于工具的业务决定本文不替你选但无论哪种都应该把错误列表完整打印或写进报告而不是只留第一条异常。五 用回归样例锁住维数边界本次独立验证脚本覆盖直接拆包的三种报错消息、字符串和字典“误通过”、迭代器失败后被消耗、长度不符报告实际个数、整条记录类型错误、分量为布尔或字符串、非有限数、批量收集全部错误以及检查前后原始数据未被修改。全过程只用内存中的自拟数据不读写真实关卡也不调用引擎接口。接手坐标相关工具时可以先准备二维、四维、字符串123、含True的三元组和None这五个样例分别喂给现有读取代码。如果有任何一个没有报错或报错里看不出是哪条记录就说明检查还停在拆包这一层。你遇到的坐标问题是少了一个分量还是类型混进了字符串可以给出一条自拟样例和当前的读取写法我们一起看应该在哪一步加检查。依据Python 3.12 语言参考 赋值语句中关于目标列表拆包时元素个数必须与目标个数相同的说明。示例和表达原创不引用具体课程信息。
阅读完成 · 觉得有帮助?