Python3 写异步 IO 方便吗?跟 NodeJS 比,有哪些不足之处。 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
balabalaguguji
V2EX    程序员

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

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

    最近了解了下 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://wwwbaidu.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().pot(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     Solana     2536 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 15:29 PVG 23:29 LAX 08:29 JFK 11:29
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86