Evandro Coan
Evandro Coan

Reputation: 9428

How to yield from __next__ method?

I thought the following code would express the desired result:

class LockIterator(object):

    def __init__(self, lock_list):
        self.lock_list = lock_list

    def __iter__(self):
        return self

    def __next__(self):

        for resource in self.lock_list:
            print( "Locking N resources" )

            yield resource
            print( "Unlocking N resources" )

        print( "Unlocking remaining resources" )
        raise StopIteration


for item in LockIterator( ["Resource 1", "Resource 2", "Resource 3"] ):
    print("Safe resource usage:", item)

But, running it on Python I got an infinity loop:

Safe resource usage: <generator object LockIterator.__next__ at 0x000001A8BDA24938>
Safe resource usage: <generator object LockIterator.__next__ at 0x000001A8BB8AEE60>
Safe resource usage: <generator object LockIterator.__next__ at 0x000001A8BDA24938>
Safe resource usage: <generator object LockIterator.__next__ at 0x000001A8BB8AEE60>
Safe resource usage: <generator object LockIterator.__next__ at 0x000001A8BDA24938>
Safe resource usage: <generator object LockIterator.__next__ at 0x000001A8BB8AEE60>
Safe resource usage: <generator object LockIterator.__next__ at 0x000001A8BDA24938>
Safe resource usage: <generator object LockIterator.__next__ at 0x000001A8BB8AEE60>
...

In my imagination, it was supposed to run like this:

Locking N resources
Safe resource usage: Resource 1
Unlocking N resources
Locking N resources
Safe resource usage: Resource 2
Unlocking N resources
Locking N resources
Safe resource usage: Resource 3
Unlocking N resources
Unlocking remaining resources

Do you know how could automatically force this behavior in a ordinary for loop?

for item in LockIterator( ["Resource 1", "Resource 2", "Resource 3"] ):
    print("Safe resource usage:", item)

Upvotes: 1

Views: 541

Answers (2)

Evandro Coan
Evandro Coan

Reputation: 9428

I also manage to fix it by deleting the __next__ and moving its body to the __iter__ method:

class LockIterator(object):

    def __init__(self, lock_list):
        self.lock_list = lock_list

    def __iter__(self):

        for resource in self.lock_list:
            print( "Locking N resources" )

            yield resource
            print( "Unlocking N resources" )

        print( "Unlocking remaining resources" )

for item in LockIterator( ["Resource 1", "Resource 2", "Resource 3"] ):
    print("Safe resource usage:", item)

Upvotes: 3

L3viathan
L3viathan

Reputation: 27283

__next__ is called every time the for loop needs a new item. Since your __next__ is a generator, that is what is returned every time.

Instead, you could get rid of the class and just write a generator:

def LockIterator(lock_list):
    # better name this lockify or something else in snake_case
    for resource in lock_list:
        print("Locking N resources")

        yield resource
        print("Unlocking N resources")

    print("Unlocking remaining resources")

Upvotes: 2

Related Questions