shodanex
shodanex

Reputation: 15406

Detecting python coroutine called as subroutine

Let's say I have a bunch of coroutines. These coroutines should be called using yield from. How can I detect I made a mistake and directly called the coroutine like a subroutine ?

Here is an example code :

import asyncio

@asyncio.coroutine
def A(msg):
    print(msg)
    yield from asyncio.sleep(1)

@asyncio.coroutine
def B():
    while True :
        yield from A('1')
        A('2')
        yield from A('3')

loop = asyncio.get_event_loop()
loop.run_until_complete(B())

And the output :

1
3
1
3
1
3
...

Calling coroutine like subroutine does nothing, but does not raise exception or block the event loop, so the failure mode is very quiet.

Upvotes: 1

Views: 259

Answers (1)

Patrick Maupin
Patrick Maupin

Reputation: 8127

I added a check_iterator function to your code. If you decorate your coroutine with this function, it will print information out if your coroutine is ever directly called and is not accessed using __iter__. Depending on your actual code, you may need to make a more complete implementation and wrap __next__ as well. That would probably be somewhat lower-performance, though.

import asyncio
import functools

class IterWrap(object):
    def __init__(self, f, *args, **kwds):
        self.iterated = False
        self.info = [f, args, kwds]
        self.f = f(*args, **kwds)

    def __iter__(self):
        self.iterated = True
        return iter(self.f)

    def __del__(self):
        if not self.iterated:
            print("Did not yield: %s" % self.info)

def check_iterator(f):
    @functools.wraps(f)
    def wrapper(*args, **kwds):
        return IterWrap(f, *args, **kwds)
    return wrapper

@check_iterator
@asyncio.coroutine
def A(msg):
    print(msg)
    yield from asyncio.sleep(1)

@asyncio.coroutine
def B():
    while True :
        yield from A('1')
        A('2')
        yield from A('3')

loop = asyncio.get_event_loop()
loop.run_until_complete(B())

The results of running this on Python 3.4 are:

1
Did not yield: [<function A at 0xb6e3189c>, ('2',), {}]
3
1
Did not yield: [<function A at 0xb6e3189c>, ('2',), {}]
3
1
Did not yield: [<function A at 0xb6e3189c>, ('2',), {}]
3
1
Did not yield: [<function A at 0xb6e3189c>, ('2',), {}]
3

Upvotes: 4

Related Questions