pythonpython-asyncio

Check for __contains__ in AsyncGenerator/AsyncIterator


In synchronous python, I can check if an iterator contains a value using the in operator:

def my_iterator():
    print('A')
    yield 1
    print('B')
    yield 2
    print('C')
    yield 3 


print(2 in my_iterator())
# A
# B
# True

However, if I try to do this with an AsyncGenerator, there is no real way to do this with the in operator. I could make my own function to advance in the iterator, but I'm wondering if Python supports something like this by default:

import asyncio 

async def my_iterator():

    print('A')
    yield 1
    print('B')
    yield 2
    print('C')
    yield 3 

async def main():
    print(2 in my_iterator()) # argument of type 'async_generator' is not iterable


Solution

  • There is no async counterpart of the in operator.

    Also please note that __contains__ mentioned in the question title does not play a role in the posted example code. The details are documente here. The short version is that leaving aside strings, sequences, etc. there are three membership test methods for user defined objects:

    1. if __contains__ method exists, it is called,
    2. otherwise if the object is iterable, it is iterated over,
    3. last resort: values at indices 0, 1, ... etc. are tested.

    A generator (i.e. a function with yield statements) does not have a __contains__ special method (step 1 is skipped), that's why it is iterated over (step 2).

    To support async iterations, the step 2 would need a change. I don't know if it is possible, but I am quite sure the existing in won't be changed.

    That leaves us with two options for async iterables:

    1. write an async generator (a class with __aiter__ and __anext__) with its own __contains__. You could then use the existing in. However, in most cases it is impossible to know the future values.

    2. just write an own test like you've mentioned in the question:

    async for v in my_async_gen():
        if v == some_value:
            print("found")
            break
    else:
        print("not found")