编程题中@cache装饰的方法传列表触发unhashable type: 'list'错误的原因是什么?
触发TypeError: unhashable type: 'list'的根本原因,是你给被@cache装饰的uniquePathsHelper方法,传入了不可哈希的二维列表参数obstacleGrid。
这里需要明确functools.cache的运行机制:
这个装饰器会在每次调用被装饰的函数时,对所有传入的实参做哈希计算,把计算结果作为键存入内部的缓存字典,匹配同参数下之前算过的结果,避免重复计算。而所有可变类型(比如Python的list)都是不可哈希的,无法作为字典的键,因此在生成缓存键的阶段就直接抛出了错误。
可以对应解释你遇到的“奇怪现象”:
因为整个哈希校验、缓存键生成的逻辑,是@cache装饰器包裹的外层包装函数做的,在你触发调用、传入obstacleGrid参数的瞬间就会执行校验,根本不会走到uniquePathsHelper内部的业务代码。所以堆栈追踪会直接停在你调用self.uniquePathsHelper(obstacleGrid,start)的行上,给人一种“程序没进入helper、没找到哈希逻辑”的错觉。
补充说明:普通函数传入列表参数不会有任何问题,因为列表本身就是用来做可变数据操作的,不需要对自身做哈希。只有当某个逻辑需要把列表值作为字典键、存入哈希集合时,才会因为列表不可哈希报错——而
@cache的内部实现,刚好就是用字典存所有调用过的参数结果的。
你在主方法里看不到手动对列表做哈希的代码,是因为这个哈希操作根本不是你写的,是装饰器隐式执行的。很多人用@cache做递归记忆化的时候,都会忘了这个装饰器对入参的可哈希要求,只要传了列表、字典这类可变类型就会触发同类报错。
- 移除递归helper方法里的矩阵参数:因为整个路径遍历过程中,障碍物网格是固定不变的,不需要每次递归都把整个矩阵当参数传递。可以在主方法里把矩阵存到类实例的属性上,比如
self.grid = obstacleGrid,让递归helper只接收可哈希的坐标元组pt作为参数,这样@cache需要处理的参数就只有self和坐标元组,全部是可哈希类型,报错自然消失。 - 如果不想改参数结构,可以放弃使用
functools.cache,改用手动维护的记忆化字典:单独建一个字典memo = {},递归时把坐标元组当键存计算结果,完全绕开装饰器对入参可哈希的强制要求。 - 额外提示:你当前的逻辑还漏了几个边界判断,比如起点本身就是障碍物、终点被障碍物遮挡的场景,修复完缓存报错后记得补全这些判断。
内容的提问来源于stack exchange,提问作者Campbell Hutcheson

