V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
balabalaguguji
V2EX  ›  程序员

Python3 写异步 IO 方便吗?跟 NodeJS 比,有哪些不足之处。

  •  1
     
  •   balabalaguguji · 2021-08-23 17:36:34 +08:00 · 3384 次点击
    这是一个创建于 948 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近了解了下 Python3 的 async/await 用起来跟 NodeJS 的差不多,找到的异步 Redis 和 Mongodb 库都还不错。

    发现 requests 没有异步,想找个替代的,aiohttp 的语法太奇怪了,如下,得先创建一个 session,然后 xxx,写起来很是麻烦,特别是要把以前的同步代码改为异步的,突然想要放弃。

    import aiohttp
    import asyncio
    
    async def main():
        async with aiohttp.ClientSession() as session:
            pokemon_url = 'https://pokeapi.co/api/v2/pokemon/151'
            async with session.get(pokemon_url) as resp:
                pokemon = await resp.json()
                print(pokemon['name'])
    
    asyncio.run(main())
    

    NodeJS 的 Promise 就非常爽,没有异步的自己包装下就好了,像 sleep 。

    34 条回复    2021-08-25 09:58:09 +08:00
    Vegetable
        1
    Vegetable  
       2021-08-23 17:40:57 +08:00
    异步网络请求库都是这模样。
    https://github.com/encode/httpx

    > HTTPX builds on the well-established usability of requests
    HongTang
        2
    HongTang  
       2021-08-23 17:43:16 +08:00
    python 不是假并发吗
    kasheemlew
        3
    kasheemlew  
       2021-08-23 17:49:52 +08:00
    python 也可以这样:

    ```python
    import asyncio
    import requests

    async def main():
    loop = asyncio.get_event_loop()
    futures = [
    loop.run_in_executor(None, requests.get, 'http://www.baidu.com')
    for _ in range(100)
    ]
    await asyncio.gather(*futures)

    asyncio.run(main())
    ```
    libook
        4
    libook  
       2021-08-23 18:13:24 +08:00
    只要从同步代码重构为 async/await,基本都是要一层一层都改成 async/await 写法,包括 JS 在内的各个语言都是这样的。

    aiohttp 的这个流程跟 Node.js 的 http module 基本是一致的,都是:
    1. 创建 HTTP 客户端;
    2. 创建请求;
    3. 向请求流中写入数据,然后发送流结束;
    4. 从返回流接收数据,直到流结束。
    balabalaguguji
        5
    balabalaguguji  
    OP
       2021-08-23 18:14:29 +08:00
    @libook #4 axios 好用,python 没找到那么好用的。
    LeeReamond
        6
    LeeReamond  
       2021-08-23 18:15:51 +08:00   ❤️ 2
    1 、requests 的正常用法也包括创建 session,不用只是因为写的 demo 需求场景太简单了而已。

    2 、因为 Python 存在同步宇宙与异步宇宙两种东西,你要在同步宇宙里创造异步宇宙 j 就需要手搓一个事件循环,所以采用了这种写法。node 的事件循环是与生俱来送给你的,所以你在 node 中不需要额外写法,但同样地这令进入同步宇宙变得困难。

    3 、Python 也可以非常简单地将同步逻辑封装为协程。
    aladdinding
        7
    aladdinding  
       2021-08-23 18:16:02 +08:00
    使用了 session 应该是会和浏览器一样进行 tcp 的连接复用 跟浏览器一样
    aladdinding
        8
    aladdinding  
       2021-08-23 18:16:29 +08:00
    requests 也有 session
    janxin
        9
    janxin  
       2021-08-23 18:17:24 +08:00
    最大问题是侵入性的,至少要做额外适配

    客户端推荐 httpx
    keepeye
        10
    keepeye  
       2021-08-23 18:36:51 +08:00
    自己可以封装一下,例如

    async def post(url, data: bytes, proxy=None, **kwargs):
    async with aiohttp.ClientSession().post(url, data=data, proxy=proxy, **kwargs) as response:
    return response
    keepeye
        11
    keepeye  
       2021-08-23 18:37:26 +08:00
    缩进怎么没了 ,谁知道评论怎么发代码?
    ```
    async def post(url, data: bytes, proxy=None, **kwargs):
    async with aiohttp.ClientSession().post(url, data=data, proxy=proxy, **kwargs) as response:
    return response
    ```
    balabalaguguji
        12
    balabalaguguji  
    OP
       2021-08-23 18:46:23 +08:00
    @keepeye #10 好嘞,感谢
    ClericPy
        13
    ClericPy  
       2021-08-23 20:39:44 +08:00
    我是自己缓存一个 Session 对象然后到处引用...

    比如这句 async with aiohttp.ClientSession() as session 拆成 self.session = aiohttp.ClientSession() 然后 await self.session.__aenter__() 到 结束时候 await self.session.__aexit__(None, None None) 就行了

    用起来还凑合吧, 至于 httpx, 性能比 aiohttp 差一倍多, 不得不选了后者, 然后自己加了 wrapper 把 aiohttp 打包成 requests 那个用法...
    ysc3839
        14
    ysc3839  
       2021-08-23 20:42:53 +08:00 via Android   ❤️ 1
    @LeeReamond “需要手搓一个事件循环”这是 Python 加的限制。理论上无栈协程是可以直接替代回调函数使用的,不需要什么事件循环。比如 C++的协程在 await 一个对象的时候,被 await 的对象能拿到这个协程的回调函数,执行这个回调函数就能恢复协程执行。
    js 也与此类似,它的异步跟事件循环关系并不大。
    但是 Python 的不一样,它一定要一个事件循环或者说调度器才能跑起来,不能像 C++那样让被等待方控制恢复执行。
    ericls
        15
    ericls  
       2021-08-23 21:06:07 +08:00
    测过 uvloop 性能和 nodejs 一模一样. 但是 CPU heavy 的东西还是慢一些
    crclz
        16
    crclz  
       2021-08-23 21:43:39 +08:00
    一点也不奇怪,每一个 async,await 都有其存在的价值。只要理解了就好了。

    https://gist.github.com/crclz/2308cc72cc3f37836a6cab22c1981849
    iyaozhen
        17
    iyaozhen  
       2021-08-23 23:31:16 +08:00
    个人感觉新业务可以用
    但前提你要保证你自己确实对这个深入理解了,不然出问题没人帮你 java 那帮人不了解这个

    因为 Python 天生是同步的,一旦出了问题你就得背锅了,之前有个对口的同事就这样走人了
    chenqh
        18
    chenqh  
       2021-08-23 23:40:44 +08:00
    @iyaozhen 这么惨?
    kuangwinnie
        19
    kuangwinnie  
       2021-08-24 07:00:10 +08:00
    @keepeye 评论不能发代码
    abersheeran
        20
    abersheeran  
       2021-08-24 09:09:20 +08:00   ❤️ 1
    跟 Nodejs 比的不足之处大抵在于 Nodejs 天然自带一个 loop,Python 需要你显式创建 loop 。而且 Nodejs 里原生都是异步的,不需要自己注意。Python 里原生都是同步的,需要自己时刻注意。
    qW7bo2FbzbC0
        21
    qW7bo2FbzbC0  
       2021-08-24 10:01:17 +08:00
    @crclz #16 最新的 HTTPClient 不是自带 async 方法了吗
    Nich0la5
        22
    Nich0la5  
       2021-08-24 10:12:54 +08:00
    python 异步几个蛋疼的点 await 传染,具体的异步实现依赖于第三方库而且场景覆盖不全,像 aiohttp aiofile 一直有海量 bug,( httpx 相对好一些维护的人比较多),自己从头撸一个异步库又要从底开始太麻烦了,我就是嫌麻烦才用 py 的。我自己写的时候经常是线程协程混写,只有明显协程性能占优的场景才用。

    至于你说的 session 问题,request 也有,而且推荐这种写法,你可以自己测下性能,复用 session 和不带的差距还是很大的
    wangyzj
        23
    wangyzj  
       2021-08-24 10:23:01 +08:00
    天生异步和假异步
    python 不是干这个的
    能写而已
    zzlhr
        24
    zzlhr  
       2021-08-24 10:26:25 +08:00
    python 写轮询 http 都能假死。。。
    enrolls
        25
    enrolls  
       2021-08-24 11:38:28 +08:00
    使用 3.9 版本,語法會變簡單。或者試試
    enrolls
        26
    enrolls  
       2021-08-24 11:38:49 +08:00
    使用 3.9 版本,語法會變簡單。或者試試 curio
    mmdsun
        27
    mmdsun  
       2021-08-24 13:17:27 +08:00 via Android
    async/await 还是 C sharp 最舒服。

    其他语言有 async/await 但没学到 C#异步的精髓。
    meiyoumingzi6
        28
    meiyoumingzi6  
       2021-08-24 20:36:02 +08:00
    主要还是生态吧, 感觉写起来好点, 单还是不太爽
    还是 golang 写异步爽的起飞
    Trim21
        29
    Trim21  
       2021-08-24 23:18:34 +08:00
    如果你要替换 requests 的话应可以用 httpx.AsyncClient,基本上就是把 requests.Session 的 http 请求换成了异步的。

    如果是长时间运行的服务的话本来就不应该用 requests.get ,应该整个程序初始化一个或者多个 requests.Session,然后复用 session,跟 aiohttp 强制你要做的事情是差不多的,aiohttp 的文档里面也提到了不要每次请求都创建一个 session
    lewinlan
        30
    lewinlan  
       2021-08-25 02:29:48 +08:00 via Android
    都 2021 年了,还写 py,放过自己好吗?
    balabalaguguji
        31
    balabalaguguji  
    OP
       2021-08-25 09:04:19 +08:00
    @Trim21 #29 好的,多谢。我的连接都是一次一个的,似乎没必要保持一个 session
    molika
        32
    molika  
       2021-08-25 09:15:53 +08:00
    习惯就好了 ~ 可能需要注意的就是各种三方库了 好多都是同步的。要自己 hack 打补丁 很痛苦。
    gitopen
        33
    gitopen  
       2021-08-25 09:26:42 +08:00
    @lewinlan 不写 py,写啥
    balabalaguguji
        34
    balabalaguguji  
    OP
       2021-08-25 09:58:09 +08:00
    @molika #32 想想还是放弃了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1278 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 17:49 · PVG 01:49 · LAX 10:49 · JFK 13:49
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.