pythonpython-asyncio

Python asyncio "contains"


Can the __contains__ function by adapted to Python asyncio, where the method becomes a coroutine? For example

class A():
    async def __contains__(self, a):
        return True

async def main():
    a = A()
    print(2 in a)

Of course this generates the warning

RuntimeWarning: coroutine 'A.__contains__' was never awaited

I assume there needs to be some special version of in for asyncio, though I've tried several permutations of using await and in with no luck.

My real use case of contains is a wrapper around a database search to check if there is an entity conflict. One case is to check if a user's screen name already exists in a database table, such as 'mike' in Users

Of course I could make my own contains and do something like User.contains('mike') but I prefer the beauty of in.


Solution

  • No - the built-in in operator is naturally synchronous, and making it make use of an asynchronous iterator needs changes on the language side.

    original answer

    Just see, for an analogy, that when the language introduced support to asynchronous context managers and asynchronous iterators, there where syntax changes introducing the async with and async for versions.

    If you want a hacky toy, not suitable for production anyway, you might play along calling (synchronously) loop._run_once() inside your __contains__ code, where you would yield to the async loop with an await. It is an internal structure, so if there is a custom event-loop running this will simply fail - and also, it is not designed to be re-entrant, so if it is called from an asynchronous function, _run_once will be on the call stack already: you results might range from it doing nothing, to a lot of state information both on your async loop and tasks to be corrupted. As a hacky toy, it could be a valid experience though.

    update

    There almost is a hacky path to attain that, but it is blocked

    While for some dunder special methods it is possible to either simply declare them as an async function or return an awaitable (in which case even a synchronous function can return a co-routine or other async-aware object that can be awaited), the in operator will coerce whatever the special method __contains__ returns to a boolean. The ordinary workings of the in operator will call __bool__ in whatever __contains__ returns to coerce it to a bool. But the __bool__ method itself must return either True or False and no other object, or a TypeError will be raised - and so, it is not possible to have in operating asynchronously, even if it returns an awaitable object. (but check the original answer, before this update)

    To be clear, it is possible to have an async async def __add__(self, other): ... method, for example, and do await (myinstance + operand) - that will work (up to Python 3.13 at least), but no with the in operator and __contains__ call.