关于BinarySearchTree中__iter__方法yield用法的几个疑问
关于二叉搜索树
__iter__生成器方法的问题解答 问题1:elem变量的类型判断
你调试时看到elem同时出现两种类型,核心原因是你贴出的__iter__代码本身存在逻辑错误,其次和生成器递归迭代的逻辑有关:
- 正常设计下,
for elem in self.left本质是触发左子节点(Node类实例)的__iter__方法,拿到该方法yield出来的值。如果__iter__逻辑正确,所有yield的都是节点存储的val属性(比如你提到的浮点数),那elem的类型就应该和val的类型一致。 - 你看到elem出现Node类实例的情况,大概率是两处问题导致:要么是你代码中其他地方误把Node实例赋值给了某个节点的
val属性;要么是你修改过迭代逻辑,某个分支直接yield了self(Node实例)而非self.val。 - 额外提一句,你当前贴的
__iter__代码存在致命逻辑问题:yield self.val被写在了if self.left != None的判断块内部,意味着只要某个节点没有左子节点,它自身的val永远不会被返回,这也会导致你调试时出现值缺失、类型不符合预期的问题。
问题2:yield elem和yield self.val的区别,以及生成器对象的数量
- 两者的作用完全不同:
yield self.val是把当前节点存储的数值,直接返回给上层的迭代调用者yield elem是把当前节点从左/右子树的迭代中拿到的元素,透传给上层调用者,本质是递归迭代的中转逻辑
- 生成器对象的数量:每次调用一个节点的
__iter__方法就会生成一个独立的生成器对象。遍历一棵有N个节点的二叉搜索树时,每个节点的__iter__都会被调用一次,因此一共会生成N个生成器对象。
问题3:生成器函数的调试经验
- 打印日志快速定位:在每处yield语句前加打印,输出当前节点的标识、即将产出的值的类型和内容,就能直观看到每一步的产出是否符合预期。
- 手动步进调试:你可以手动调用
next()方法一步步触发生成器执行,比如先拿到迭代器it = iter(树对象),每次执行print(next(it)),就能看到每一步返回的值,卡壳的位置直接对应查对应节点的逻辑即可。 - 先看整体输出再定位:直接把生成器转成列表
print(list(树对象)),对比你预期的中序遍历结果,先确定是哪个位置的值出了错,再反向找对应节点的逻辑问题,比一步步调试效率高很多。 - 先对齐普通递归逻辑:这个生成器本质就是递归实现的中序遍历,你可以先写一个普通的递归打印中序遍历结果的函数,逻辑对齐之后再对应改成yield的写法,就很容易理解执行流程。
修正后的正确__iter__代码参考
def __iter__(self): if self.left is not None: for elem in self.left: yield elem # 将yield self.val移到左子树判断的外部 yield self.val if self.right is not None: for elem in self.right: yield elem
内容的提问来源于stack exchange,提问作者Tan Phan
相关产品推荐
相关产品推荐

