为什么我们需要`asyncfor`和`asyncwith`?

引入async for和的意义async with何在?我知道这些陈述有 PEP,但它们显然是为语言设计者准备的,而不是像我这样的普通用户。将不胜感激补充示例的高级理由。

我自己做了一些研究并找到了这个答案:

async forasync with语句必要的,因为你会打破yield from/await与裸链forwith报表。

作者没有举例说明链条是如何断裂的,所以我仍然很困惑。此外,我注意到 Python 有async forand async with,但没有async whileand async try ... except。这听起来很奇怪,因为和分别是for和 的with语法糖。我的意思是,考虑到后者是前者的构建块,它们的版本不会允许更大的灵活性吗?whiletry ... exceptasync

还有另一种答案讨论async for,但它仅覆盖它是什么并不对,并没有说太多关于它是什么。

作为奖励,async forasync with语法糖?如果是,它们的详细等效形式是什么?

回答

async for并且async with是从低到高发展的逻辑延续。

过去,for编程语言中的循环过去只能简单地迭代线性索引为 0、1、2 ... 最大的值数组。

Python 的for循环是一种更高级的结构。它可以迭代任何支持迭代协议的东西,例如在树中设置元素或节点——它们都没有编号为 0、1、2 等的项目。

迭代协议的核心是__next__特殊方法。每次连续调用都会返回下一项(可能是计算值或检索到的数据)或表示迭代结束。

async for是异步的对手,而不是调用定期__next__它等待异步__anext__和其他一切保持不变。这允许在异步程序中使用常见的习惯用法:

# 1. print lines of text stored in a file
for line in regular_file:
    print(line)

# 2A. print lines of text as they arrive over the network,
#
# The same idiom as above, but the asynchronous character makes
# it possible to execute other tasks while waiting for new data
async for line in tcp_stream:
    print(line)

# 2B: the same with a spawned command
async for line in running_subprocess.stdout:
    print(line)

与的情况async with类似。总而言之:该try .. finally构造被更方便的with块取代- 现在被认为是惯用的 - 可以与支持上下文管理器协议的任何东西进行通信,__enter__以及__exit__进入和退出块的方法。自然地,以前在 atry .. finally中使用的所有内容都被重写为上下文管理器(锁、开闭调用对等)

async with又是异步__aenter____aexit__特殊方法的对应物。当用于进入或退出with块的异步代码等待新数据或锁或某些其他条件被满足时,其他任务可能会运行。

注意:与 不同for,可以使用带有普通(非异步)with语句的异步对象:with await lock:,现在已弃用或不支持。


回答

TLDR: forandwith是封装了调用相关方法的几个步骤的重要语法糖。这使得无法这些步骤之间手动添加awaits - 但正确可用/需要它。同时,这意味着获得对他们的支持至关重要。async forwithasync


为什么我们不能做await美好的事情

Python 的语句和表达式由所谓的协议支持:当某个对象用于某些特定的语句/表达式时,Python 会调用该对象上相应的“特殊方法”以允许自定义。例如,x in [1, 2, 3]委托以list.__contains__定义in实际含义。
大多数协议都很简单:每个语句/表达式都会调用一个特殊的方法。如果async我们拥有的唯一特征是原语await,那么我们仍然可以async通过await在正确的位置洒上所有这些“一种特殊方法”语句/表达式“ ” 。

与此相反,forwith陈述都对应于多个步骤:for使用迭代器协议来反复获取__next__的迭代器的项目,并with使用该上下文管理器协议来进入和退出的上下文。
重要的部分是两者都有多个可能需要异步的步骤。虽然我们可以await在其中一个步骤中手动撒一个,但我们不能全部命中。

  • 更容易查看的情况是with:我们可以分别处理__enter____exit__方法。

    我们可以天真地定义一个带有异步特殊方法的同步上下文管理器。为了进入这实际上是通过await战略性地添加一个:

    with AsyncEnterContext() as acm:
        context = await acm
        print("I entered an async context and all I got was this lousy", context)
    

    然而,它已经打破了,如果我们使用一个withfor语句情境:我们会先进入所有上下文一次,然后等待它们全部一次

    with AsyncEnterContext() as acm1, AsyncEnterContext() as acm2:
        context1, context2 = await acm1, await acm2  # wrong! acm1 must be entered completely before loading acm2
        print("I entered many async contexts and all I got was a rules lawyer telling me I did it wrong!")
    

    更糟糕的是,没有一个点可以让我们正确await 退出

虽然for和确实with是语法糖,但它们是不平凡的语法糖:它们使多个动作更好。因此,人们不能天真地对它们进行await 单独的操作。只有一条毯子async withasync for可以覆盖每一步。

为什么我们想要async美好的事物

这两个forwith抽象:他们完全封装的迭代/语境化的想法。

二者择其一,Pythonfor内部迭代的抽象——相比之下,awhile外部迭代的抽象。简而言之,这意味着for程序员不必知道迭代实际上是如何工作的。

  • 比较如何迭代listusing foror while
    some_list = list(range(20))
    index = 0                      # lists are indexed from 0
    while index < len(some_list):  # lists are indexed up to len-1
        print(some_list[index])    # lists are directly index'able
        index += 1                 # lists are evenly spaced
    
    for item in some_list:         # lists are iterable
        print(item)
    

    外部while迭代依赖于关于列表如何具体工作的知识:它可迭代对象中提取实现细节并将它们放入循环中。相比之下,内部for迭代只依赖于知道列表是可迭代的。它适用于任何列表的实现,实际上任何可迭代对象的实现。

底线是for——而且with——不要打扰实施细节。这包括需要知道哪些我们需要异步洒步骤。只有一条毯子async withasync for可以在我们不知道是哪一步的情况下覆盖每一步。

为什么我们需要async美好的事物

一个有效的问题是为什么forwith获取async变体,但其他人没有。有一个关于一个微妙的点forwith是不是在日常使用很明显:既代表并发-而并发的域async

无需过多赘述,简单的解释是处理例程 ( ())、可迭代对象 ( for) 和上下文管理器 ( with)的等效性。正如问题中引用的答案中所确定的那样,协程实际上是一种生成器。显然,生成器也是可迭代的,事实上我们可以通过生成器表达任何可迭代的。不太明显的部分是上下文管理器也相当于生成器——最重要的是,contextlib.contextmanager可以将生成器转换为上下文管理器。

为了始终如一地处理各种并发,我们需要async例程 ( await)、可迭代对象 ( async for) 和上下文管理器 ( async with) 的变体。只需一条毯子async withasync for就能始终如一地覆盖每一步。


以上是为什么我们需要`asyncfor`和`asyncwith`?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>