2017-12-31 201 views
1

에 바닐라 발생기에 비동기 발전기를 병합합니다. 내가 그들을 발견하는 유일한 방법은 iterable 및 생성기를 반환하는 이벤트 루프를 통해 있기 때문입니다. 나 간단한 예제로 이것을 설명하자 어떻게 파이썬 3.5 내가 비동기 발전기를 결합하는 데 문제가 실제로이를 실행하는거야

이의 내가 (내가 목적의 API를 사용하지 않는)을 긁어 구글을 검색하는 기능 GOOGLE_SEARCH 있다고 가정 해 봅시다. 검색 문자열을 받아서 검색 결과의 생성자를 반환합니다. 이 생성기는 페이지가 끝났을 때 끝나지 않고 다음 페이지로 계속 이동하여 기능을 계속합니다. 따라서 GOOGLE_SEARCH 함수는 가능성이 거의 무한 발전기를 돌려줍니다 (기술적으로 항상 종료됩니다하지만 종종 당신이 구글에서 검색을 위해 명중의 수백만을 얻을 수 있습니다) 그래서 지금 내가 나를 수있는 기능을 만들고 싶어,

def google_search(search_string): 
    # Basically uses requests/aiohttp and beautifulsoup 
    # to parse the resulting html and yield search results 
    # Assume this function works 
    ...... 

좋아 여러 google_search 생성기를 반복합니다. 다음과 같이하고 싶습니다.

def google_searches(*search_strings): 
    for results in zip(google_search(query) for query in search_strings): 
     yield results 

이렇게 간단한 방법으로 google_searches를 풀고 내 결과를 얻을 수 있습니다. 그리고 위의 코드는 잘 작동하지만 합리적으로 많은 수의 검색에서는 매우 느립니다. 이 코드는 첫 번째 검색에 대한 요청을 보낸 다음 두 번째 검색에 대한 요청을 최종적으로 전송할 때까지 결과를 산출합니다. 나는 이것을 (많은) 속도를 올리고 싶다. 내 첫 번째 아이디어는 비동기 함수로 google_searches를 변경하는 것입니다 (파이썬 3.6.3을 사용 중이며 await/async 등을 사용할 수 있습니다). 이것은 비동기 생성기를 생성하지만 다른 비동기 함수 나 이벤트 루프에서만 실행할 수 있습니다. 그리고 run_until_complete에 이벤트 루프에서 실행 (loop.gather는 (...)) 대신 목록에서 개최 너무 많은 검색 결과가 아마 방법이 같은 목적을 패배 일반 발전기의 결과 목록을 반환합니다.

는 바닐라 생성 할 필요 여전히 요청을 실행하는 비동기 동안로 (바람직하게는 비동기 코드 아무것도하지만이 환영을 사용)하는 방법이 google_searches 빠르게 작동 할 수 있습니까? 미리 감사드립니다.

+0

왜 downvote? 내 질문에 적절하게 질문하지 않거나 구체적이지 않은가? 나는 downvote 상관 없어하지만 난 그것을 개선 할 수 있도록 잘못 알고 싶습니다. 감사. –

답변

3
def google_search(search_string): 
    # Basically uses requests/aiohttp and beautifulsoup 

이것은 일반 동기 생성기입니다. 당신은 그 안에 requests을 사용할 수있을 것입니다,하지만 당신은 비동기 aiohttp을 사용하려는 경우, 당신은 asynchronous generatorasync def으로 정의해야합니다. 여러 비동기 발전기를 통해 반복에 관해서 무엇

는 더 흥미 롭다. 일반 zip은 비동기 iterables가 아닌 일반 iterables에서 작동하므로 사용할 수 없습니다. 그래서 당신은 자신을 구현해야한다 (동시에 iterating을 지원할 것이다).

import asyncio 
import aiohttp 
import time 


# async versions of some builtins: 
async def anext(aiterator): 
    try: 
     return await aiterator.__anext__() 
    except StopAsyncIteration as exc: 
     raise exc 


def aiter(aiterable): 
    return aiterable.__aiter__() 


async def azip(*iterables): 
    iterators = [aiter(it) for it in iterables] 
    while iterators: 
     results = await asyncio.gather(
      *[anext(it) for it in iterators], 
      return_exceptions=True, 
     ) 
     yield tuple(results) 


# emulating grabbing: 
async def request(url): 
    async with aiohttp.ClientSession() as session: 
     async with session.get(url) as resp: 
      return await resp.text() 


async def google_search(search_string): 
    for i in range(999): # big async generator 
     url = 'http://httpbin.org/delay/{}'.format(i) # increase delay to better see concurency 
     j = await request(url) 
     yield search_string + ' ' + str(i) 


async def google_searches(*search_strings): 
    async for results in azip(*[google_search(s) for s in search_strings]): 
     for result in results: 
      yield result 


# test it works: 
async def main(): 
    async for result in google_searches('first', 'second', 'third'): 
     print(result, int(time.time())) 


loop = asyncio.get_event_loop() 
try: 
    loop.run_until_complete(main()) 
    loop.run_until_complete(loop.shutdown_asyncgens()) 
finally: 
    loop.close() 

출력 :

first 0 1514759561 
second 0 1514759561 
third 0 1514759561 
first 1 1514759562 
second 1 1514759562 
third 1 1514759562 
first 2 1514759564 
second 2 1514759564 
third 2 1514759564 
first 3 1514759567 
second 3 1514759567 
third 3 1514759567 

시간 다른 검색을 동시에 실행할 것을 보여준다

나는 당신이 원하는 것을 생각 약간의 프로토 타입을 만들었다.

+0

이것은 아름답습니다. 정확히 내가 무엇을 찾고 있었는지. 고맙습니다! 그리고 새해 복 많이 받아! –