Python 3.4和3.5之间的协程,我该如何保持向后兼容性?

11
我正在开发一个使用asyncio的Python聊天机器人框架。但是我看到了PEP-492,其中有一种新的语法:async/await,并且最终被接受了。
我喜欢async/await语法,并想要使用它。但我担心它是否向后兼容3.4版本。
比如,我写了下面这样的代码:
```python async def foo(): await asyncio.sleep(1) ```
那么,其他人能在3.4版本中使用它吗?
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)

然后,我可以在py35中使用它与这个源代码。

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

但是,py34没有await语法。如果我在PyPI上上传了上述源码且没有版本约束,并且有人在py34上安装了它,它会正常工作吗?我该如何保证它的兼容性?

1个回答

13
如果您需要在代码中支持Python 3.4,则需要使用旧的@asyncio.coroutine/yield from语法。没有办法支持async/await语法,除非运行3.5;在3.4或更低版本上,在编译时会出现SyntaxError。
唯一可以以向后兼容的方式利用新功能的是在适当的位置(__aiter__、__aenter__、__aexit__等)向类添加各种__a*__方法,并使用yield from协程语法。这样,您的对象就可以支持async with/async for语句,因此使用Python 3.5运行您的库的用户可以利用新功能。
例如,这个类可以与async with一起使用,但在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)

在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())

在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())

如果你正在编写一个使用asyncio的应用程序,那么这可能并不有用,但是如果你正在开发一个旨在供其他开发人员使用的库或框架,则值得这样做。


7
看到人们把 @asyncio.coroutine 描述为“老的”构造方式,实际上这种构造方式只在最近的 Python 版本中出现,这让人感到有趣。 :-) - Mark Dickinson
@MarkDickinson 的确。Python 在这个领域的发展相当迅速。 :) - dano
1
因为从Python 2到Python 3的转换非常容易,所以Python社区决定使Python 3.4和Python 3.5之间的转换也变得简单 :-) ? - z1naOK9nu8iY5A
你会在3.4或更低版本的编译时遇到SyntaxError,这实际上是一个奇怪的问题。我们可以添加导入守卫,并仅允许3.5+的新功能,但现在我们需要为3.4和3.5+分别拥有独立的代码库。等一下,3.4是LTS Ubuntu 14.04上的标准Python,因此它将维护到2017年!我觉得这里有些问题 :) - rudyryk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接