Python 异步请求+写文件混乱
Python async requests + writing files confusion
我有一个简单的 python 异步程序,可以从特定的 URL 检索 gzip 文件。我已经将 aiohttp 用于异步请求。根据 aiohttp 文档 (https://docs.aiohttp.org/en/stable/client_quickstart.html),我在我的测试方法中使用了他们在 'Streaming Response Content' 下的示例来写入数据。
async def main(url):
async with aiohttp.ClientSession() as session:
await asyncio.gather(test(session, url))
async def test(session, url):
async with session.get(url=url) as r:
with open('test.csv.gz', 'wb') as f:
async for chunk in r.content.iter_chunked(1024):
f.write(chunk)
但是,我不确定test() 中的内容是否实际上是异步的。我读过的许多文章都提到了 'await' 关键字在异步协程中激活异步性的要求(例如 r = await session.get(url=url)) ,但我想知道 'async with' 和 'async for' 模式是否也实现了相同的目的?
我希望实现的是在执行 session.get() 时以及将数据写入本地时的异步功能,这样如果我传入许多 urls 它将) 在获取 url 时执行异步切换和 b) 在将数据写入本地时执行异步切换。
对于 b),我需要使用类似下面的东西吗?
async with aiofiles.open('test.csv.gz', 'wb') as f:
async for chunk in r.content.iter_chunked(1024):
await f.write()
这让我想到了一个有点离题的问题,但是 async with session.get(url=url) as r:
和 r = await session.get(url=url)
之间有什么区别?
请让我知道我的理解是否有缺陷,或者我是否缺少关于异步功能的一些基本知识!
好问题。看下面的小程序,它运行两个任务。每个都有一个异步上下文管理器和一个异步迭代器:
import asyncio
from contextlib import asynccontextmanager
@asynccontextmanager
async def nada():
# await asyncio.sleep(0.0)
yield
async def aloop():
for _ in range(5):
# await asyncio.sleep(0.0)
yield
async def atask(name):
async for _ in aloop():
async with nada():
print("Task", name)
async def main():
asyncio.create_task(atask("1"))
await asyncio.create_task(atask("2"))
if __name__ == "__main__":
asyncio.run(main())
输出:
Task 1
Task 1
Task 1
Task 1
Task 1
Task 2
Task 2
Task 2
Task 2
Task 2
没有发生任务切换。
现在取消注释上下文管理器 (nada
) 或迭代器 (aloop
) 中的 await asyncio.sleep(0.0)
。输出变为:
Task 1
Task 2
Task 1
Task 2
Task 1
Task 2
Task 1
Task 2
Task 1
Task 2
现在确实发生了任务切换。但是你的主程序在这两种情况下是完全一样的。
所以第一个问题的答案是 'async with' 和 'async for' 模式 不一定 导致任务切换。这取决于它们的实施。异步上下文管理器和异步迭代器都调用特殊机制;如果该机器执行 await 表达式,就会发生任务切换。但是 Python 不需要异步上下文管理器或异步迭代器来执行此操作。
这是完全合法的Python:
async def do_nothing:
pass
实际上,您可能可以信任像 aiohttp 这样广泛部署的库。将 async 关键字放在方法前面并且 not 在其中执行 await 并没有多大价值。我能想到的唯一 use-case 是当 API 需要协程但您不需要异步行为时。将这种功能放在 general-use 库中将是糟糕的设计,无论如何不是没有好的文档。
你的第二个问题 - async with session.get(url=url) as r
: 和 r = await session.get(url=url)
之间的区别是什么 - 第一种形式执行两个特殊函数而第二种形式不执行。第一个有点等同于:
try:
x = session.get(url=url)
r = await x.__aenter__()
# the indented block of code executes here
finally:
await x.__aexit__(...)
__aexit__
方法接受一些与异常处理有关的参数,您可以在文档中阅读这些参数。同步上下文管理器类似,只是特殊方法被命名为 __enter__
和 __exit__
.
我有一个简单的 python 异步程序,可以从特定的 URL 检索 gzip 文件。我已经将 aiohttp 用于异步请求。根据 aiohttp 文档 (https://docs.aiohttp.org/en/stable/client_quickstart.html),我在我的测试方法中使用了他们在 'Streaming Response Content' 下的示例来写入数据。
async def main(url):
async with aiohttp.ClientSession() as session:
await asyncio.gather(test(session, url))
async def test(session, url):
async with session.get(url=url) as r:
with open('test.csv.gz', 'wb') as f:
async for chunk in r.content.iter_chunked(1024):
f.write(chunk)
但是,我不确定test() 中的内容是否实际上是异步的。我读过的许多文章都提到了 'await' 关键字在异步协程中激活异步性的要求(例如 r = await session.get(url=url)) ,但我想知道 'async with' 和 'async for' 模式是否也实现了相同的目的?
我希望实现的是在执行 session.get() 时以及将数据写入本地时的异步功能,这样如果我传入许多 urls 它将) 在获取 url 时执行异步切换和 b) 在将数据写入本地时执行异步切换。
对于 b),我需要使用类似下面的东西吗?
async with aiofiles.open('test.csv.gz', 'wb') as f:
async for chunk in r.content.iter_chunked(1024):
await f.write()
这让我想到了一个有点离题的问题,但是 async with session.get(url=url) as r:
和 r = await session.get(url=url)
之间有什么区别?
请让我知道我的理解是否有缺陷,或者我是否缺少关于异步功能的一些基本知识!
好问题。看下面的小程序,它运行两个任务。每个都有一个异步上下文管理器和一个异步迭代器:
import asyncio
from contextlib import asynccontextmanager
@asynccontextmanager
async def nada():
# await asyncio.sleep(0.0)
yield
async def aloop():
for _ in range(5):
# await asyncio.sleep(0.0)
yield
async def atask(name):
async for _ in aloop():
async with nada():
print("Task", name)
async def main():
asyncio.create_task(atask("1"))
await asyncio.create_task(atask("2"))
if __name__ == "__main__":
asyncio.run(main())
输出:
Task 1
Task 1
Task 1
Task 1
Task 1
Task 2
Task 2
Task 2
Task 2
Task 2
没有发生任务切换。
现在取消注释上下文管理器 (nada
) 或迭代器 (aloop
) 中的 await asyncio.sleep(0.0)
。输出变为:
Task 1
Task 2
Task 1
Task 2
Task 1
Task 2
Task 1
Task 2
Task 1
Task 2
现在确实发生了任务切换。但是你的主程序在这两种情况下是完全一样的。
所以第一个问题的答案是 'async with' 和 'async for' 模式 不一定 导致任务切换。这取决于它们的实施。异步上下文管理器和异步迭代器都调用特殊机制;如果该机器执行 await 表达式,就会发生任务切换。但是 Python 不需要异步上下文管理器或异步迭代器来执行此操作。
这是完全合法的Python:
async def do_nothing:
pass
实际上,您可能可以信任像 aiohttp 这样广泛部署的库。将 async 关键字放在方法前面并且 not 在其中执行 await 并没有多大价值。我能想到的唯一 use-case 是当 API 需要协程但您不需要异步行为时。将这种功能放在 general-use 库中将是糟糕的设计,无论如何不是没有好的文档。
你的第二个问题 - async with session.get(url=url) as r
: 和 r = await session.get(url=url)
之间的区别是什么 - 第一种形式执行两个特殊函数而第二种形式不执行。第一个有点等同于:
try:
x = session.get(url=url)
r = await x.__aenter__()
# the indented block of code executes here
finally:
await x.__aexit__(...)
__aexit__
方法接受一些与异常处理有关的参数,您可以在文档中阅读这些参数。同步上下文管理器类似,只是特殊方法被命名为 __enter__
和 __exit__
.