Coroutine in python between 3.4 and 3.5, How can I

2019-03-15 12:32发布

问题:

I'm on developing python chatting bot framework with asyncio. But I watch PEP-492 and there is new syntax, async/await and finally it accepted.

I like async/await syntax and I want to use it. but I worry about 3.4 backwords compatibility.

If I use new syntax in my code, someone can use it in 3.4?

For example, I write some code like this,

import asyncio

class ChatBot:
    def __init__(self, loop):
        self.loop = loop

    async def connect(self):
        self.reader, self.writer = await asyncio.open_connect(HOST, PORT, loop=self.loop)

    async def read():
        return await self.reader.read()

    async def run(self):
        running = True
        while running:
            try:
                await self.connect()
                line = await self.read()
                if not line:
                    continue
                await self.parse(line)
            except BotInternalError as e:
                if e.stop:
                    running = False
                    break
            except:
                pass

    async def parse(self, msg):
        if msg.startswith('PING'):
            self.pong()
        elif msg.startswith('ERROR'):
            self.error()
        else:
            await self.some_work(msg)

    async def some_work(self, msg):
        # some looooooooong works
        self.send(msg)

    def send(self, msg):
        self.writer.write(msg)

Than, I can use it with this source in py35

loop = asyncio.get_event_loop()  # I don't know it really needed in py35.
bot = ChatBot(loop)
asyncio.run_until_complete(bot.run())

But, py34 don't have await syntax. If I uploaded above source at PyPI without version constraint and someone installed it on py34, It'll work fine? How can I keep it?

回答1:

If you need to support Python 3.4 in your code, you'll need to use the old @asyncio.coroutine/yield from style syntax. There's no way to support the async/await syntax without running 3.5; you'll get a SyntaxError at compilation time on 3.4 or lower.

The only thing that takes advantage of the new features you can do in a backwards-compatible way is add the various __a*__ methods to your classes where appropriate (__aiter__, __aenter__, __aexit__, etc.), using the yield from coroutine syntax. That way, your objects can support async with/async for statements, so that users of your library running Python 3.5 could take advantage of the new features.

For example, this class can be used with async with, but won't break when run on Python 3.4:

import asyncio

class Test:
    def __enter__(self):
        return self

    def __exit__(self, *args):
        print("arg")

    @asyncio.coroutine
    def __aenter__(self):
        yield from self.init_state()
        return self

    @asyncio.coroutine
    def init_state(self):
        yield from asyncio.sleep(2) # Pretend this is real initialization

    @asyncio.coroutine
    def __aexit__(self, *args):
        return self.__exit__(self, *args)

On Python 3.5:

import asyncio
from test import Test

async def main():
    print("entering with")
    async with Test() as t:
        print("in here")

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

On Python 3.4

import asyncio
from test import Test

@asyncio.coroutine
def oldmain():
    print("entering with")
    with Test() as t:
        yield from t.init_state()
        print("in here")

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

This probably isn't useful if you're writing an application that uses asyncio, but if you're developing a library or framework intended to be used by other developers, it's worth doing.