How to handle async and inheritance?
Dieter Maurer
dieter at handshake.de
Wed Jul 1 13:35:20 EDT 2020
Stephen Rosen wrote at 2020-6-30 11:59 -0400:
>Hi all,
>
>I'm looking at a conflict between code sharing via inheritance and async
>usage. I would greatly appreciate any guidance, ideas, or best practices
>which might help.
>
>I'll speak here in terms of a toy example, but, if anyone wants to look at
>the real code, I'm working on webargs. [1] Specifically, we have a `Parser`
>class and an `AsyncParser` subclass, and the two have a lot of code
>duplication to handle async/await. [2]
>
>
>I've got an inheritance structure like this:
>
>class MyAbstractType: ...
>class ConcreteType(MyAbstractType): ...
>class AsyncConcreteType(MyAbstractType): ...
>
>One of my goals, of course, is to share code between ConcreteType and
>AsyncConcreteType via their parent.
>But the trouble is that there are functions defined like this:
>
>class MyAbstractType:
> def foo(self):
> x = self.bar()
> y = self.baz(x)
> ... # some code here, let's say 20 lines
>
>class AsyncConcreteType(MyAbstractType):
> async def foo(self):
> x = await self.bar()
> y = self.baz(x)
> ... # the same 20 lines as above, but with an `await` added
>every-other line
>
>
>I'm aware that I'm looking at "function color" and that my scenario is
>pitting two language features -- inheritance and async -- against one
>another. But I don't see a clean way out if we want to support an
>"async-aware" version of a class with synchronous methods.
>
>What I tried already, which I couldn't get to work, was to either fiddle
>with things like `inspect` to see if the current function is async or to
>use a class variable to indicate that the current class is the async
>version. The idea was to write something like
>
>class MyAbstractType:
> _use_async_calls = False
> def foo(self):
> x = self._await_if_i_am_async(self.bar)
> y = self.baz(x)
> ...
>
>and that way, the async subclass just needs to change signatures to be
>async with little stubs and set the flag,
>
>class AsyncConcreteType(MyAbstractType):
> _use_async_calls = True
> async def foo(self):
> return super().foo()
> async def bar(self):
> return super().bar()
As far as I understand (I am far from an `async` expert),
`async` functions need to be specially compiled. This implies
that there cannot be a runtime switch which makes a given function
asynchronous or synchronous at runtime.
You would need to have 2 functions, one asynchronous and one synchronous.
Then a runtime switch may select the appropriate function.
You likely can generate one of those function kinds from the other
one by an `ast` (= "Abstract Syntax Tree") transformation.
More information about the Python-list
mailing list