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

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

  •  1
     
  •   balabalaguguji · 65 天前 · 2131 次点击
    这是一个创建于 65 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近了解了下 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   65 天前
    异步网络请求库都是这模样。
    https://github.com/encode/httpx

    > HTTPX builds on the well-established usability of requests
    HongTang
        2
    HongTang   65 天前
    python 不是假并发吗
    kasheemlew
        3
    kasheemlew   65 天前
    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   65 天前
    只要从同步代码重构为 async/await,基本都是要一层一层都改成 async/await 写法,包括 JS 在内的各个语言都是这样的。

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

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

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

    客户端推荐 httpx
    keepeye
        10
    keepeye   65 天前
    自己可以封装一下,例如

    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   65 天前
    缩进怎么没了 ,谁知道评论怎么发代码?
    ```
    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   65 天前
    @keepeye #10 好嘞,感谢
    ClericPy
        13
    ClericPy   65 天前
    我是自己缓存一个 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   65 天前 via Android   ❤️ 1
    @LeeReamond “需要手搓一个事件循环”这是 Python 加的限制。理论上无栈协程是可以直接替代回调函数使用的,不需要什么事件循环。比如 C++的协程在 await 一个对象的时候,被 await 的对象能拿到这个协程的回调函数,执行这个回调函数就能恢复协程执行。
    js 也与此类似,它的异步跟事件循环关系并不大。
    但是 Python 的不一样,它一定要一个事件循环或者说调度器才能跑起来,不能像 C++那样让被等待方控制恢复执行。
    ericls
        15
    ericls   65 天前
    测过 uvloop 性能和 nodejs 一模一样. 但是 CPU heavy 的东西还是慢一些
    crclz
        16
    crclz   65 天前
    一点也不奇怪,每一个 async,await 都有其存在的价值。只要理解了就好了。

    iyaozhen
        17
    iyaozhen   65 天前
    个人感觉新业务可以用
    但前提你要保证你自己确实对这个深入理解了,不然出问题没人帮你 java 那帮人不了解这个

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

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

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

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