First, there is an indentation issue in the code you shared: yield self.val
should not be in the if
block:
def __iter__(self):
if self.left != None:
for elem in self.left:
yield elem
yield self.val # Unconditional. This is also the base case
if self.right != None:
for elem in self.right:
yield elem
To understand this code, first start imagining a tree with just one node. Let's for a moment ignore the BinarySearchTree
class and say we have direct access to the Node
class. We can create a node and then iterate it:
node = Node(1)
for value in node:
print(value)
This loop will call the __iter__
method, which in this case will not execute any of the if
blocks, as it has no children, and only execute yield self.val
. And that is what value
in the above loop will get as value, and which gets printed.
Now extend this little exercise with 2 more nodes:
node = Node(1,
Node(0),
Node(2)
)
for value in node:
print(value)
Here we have created this tree, and node
refers to its root
1 <-- node
/ \
0 2
When the for..in
loop will call __iter__
now, it will first enter the first if
block, where we get a form of recursion. With the for
statement there, we again execute __iter__
, but this time on the left child of node
, i.e. the node with value 0. But that is a case we already know: this node has no children, and we know from the first example above, that this results in one iteration where the loop variable will be the value of that node, i.e. 0, and that value is yielded. That means the main program gets an iteration with value
equal to 0, which gets printed.
So elem
is numeric. It would better have been called value
or val
to take away any confusion.
After that if
block has executed we get to yield self.val
. self
is here node
, and so we yield 1. That means the main program gets to execute a second iteration, this time with value
equal to 1.
Finally the second if
block is executed, and now the right child of node
is the subject of a recursive __iter__
call. It is the same principle as with the left child. This yields value 2, and the main program prints 2.
We could again extend the tree with more nodes, but the principle is the same: by recursive calls of __iter__
all the values of the tree are yielded.
yield from
There is a syntax that allows simplification of the code, and also it is more common practice to use the is
operator when comparing with None
:
def __iter__(self):
if self.left is not None:
yield from self.left
yield self.val
if self.right is not None:
yield from self.right
This results in the same behavior. yield from
will yield all values that come from the iterable. And since node instances are iterable as they have the __iter__
method, this works as intended.