2015-08-22 16 views
7

Python 3'ün Asyncio kütüphanesiyle ilgili etrafımı sarmakta güçlük çekiyorum. Bir zipcodes listesi var ve her zipcodes karşılık gelen şehir ve eyalet almak için bir API için async çağrıları yapmaya çalışıyorum. Bunu bir for döngüsü ile başarılı bir şekilde yapabilirim ancak büyük bir zipkod listesi durumunda daha hızlı yapmak istiyorum. Asyncio ile birden çok arama yapmak ve sonucu bir sözlüke eklemek

Bu

Bu çok takdir edilir

import asyncio 
import urllib.request, json 

zips = ['90210', '60647'] 
zip_cities = dict() 

@asyncio.coroutine 
def get_cities(zipcodes): 
    url = 'http://maps.googleapis.com/maps/api/geocode/json?address='+zipcode+'&sensor=true' 
    response = urllib.request.urlopen(url) 
    string = response.read().decode('utf-8') 
    data = json.loads(string) 
    city = data['results'][0]['address_components'][1]['long_name'] 
    state = data['results'][0]['address_components'][3]['long_name'] 
    zip_cities.update({idx: [zipcode, city, state]}) 

loop = asyncio.get_event_loop() 
loop.run_until_complete([get_cities(zip) for zip in zips]) 
loop.close() 
print(zip_cities) # doesnt work 

Herhangi bir yardım zaman uyumsuz bunu yapmaya çalışıyorum benim korkunç işlevsel olmayan girişimdir

import urllib.request, json 

zips = ['90210', '60647'] 

def get_cities(zipcodes): 
    zip_cities = dict() 
    for idx, zipcode in enumerate(zipcodes): 
     url = 'http://maps.googleapis.com/maps/api/geocode/json?address='+zipcode+'&sensor=true' 
     response = urllib.request.urlopen(url) 
     string = response.read().decode('utf-8') 
     data = json.loads(string) 
     city = data['results'][0]['address_components'][1]['long_name'] 
     state = data['results'][0]['address_components'][3]['long_name'] 
     zip_cities.update({idx: [zipcode, city, state]}) 
    return zip_cities 

results = get_cities(zips) 
print(results) 
# returns {0: ['90210', 'Beverly Hills', 'California'], 
#   1: ['60647', 'Chicago', 'Illinois']} 

çalışır benim orijinal bir örnektir. İnternette karşılaştığım tüm dersler kafamın üzerinde biraz farklı görünüyor.

Not: aiohttp kullanımından bazı örnekler gördüm. Mümkünse yerli Python 3 kütüphaneleri ile uğraşmayı umuyordum.

cevap

7

HTTP isteğini yapmak için urllib kullanırsanız, eşzamanlı bir kitaplık olduğu için herhangi bir eşzamanlılık elde edemezsiniz. coroutine numaralı telefona urllib numaralı çağrıları çağıran bu işlevi değiştirmez. Sen aiohttp gibi asyncio entegre oluyor uyumsuz bir HTTP istemcisi, kullanmak zorunda:

import asyncio 
import json 
import aiohttp 

zips = ['90210', '60647'] 
zip_cities = dict() 

@asyncio.coroutine 
def get_cities(zipcode,idx): 
    url = 'https://maps.googleapis.com/maps/api/geocode/json?key=abcdfg&address='+zipcode+'&sensor=true' 
    response = yield from aiohttp.request('get', url) 
    string = (yield from response.read()).decode('utf-8') 
    data = json.loads(string) 
    print(data) 
    city = data['results'][0]['address_components'][1]['long_name'] 
    state = data['results'][0]['address_components'][3]['long_name'] 
    zip_cities.update({idx: [zipcode, city, state]}) 

if __name__ == "__main__":   
    loop = asyncio.get_event_loop() 
    tasks = [asyncio.async(get_cities(z, i)) for i, z in enumerate(zips)] 
    loop.run_until_complete(asyncio.wait(tasks)) 
    loop.close() 
    print(zip_cities) 

Senin sadece stdlib kullanmayı tercih biliyorum ama asyncio kütüphane HTTP istemcisi içermez, bu yüzden olurdu Temel olarak işlevselliğini yeniden oluşturmak için aiohttp parçalarını yeniden uygulamak. Bence başka bir seçenek urllib çağrıları arka plan iş parçacığında yapmaktır, böylece olay döngüsünü engellemezler, ancak aiohttp kullanılabilir olduğunda ne tür aptalcalar olurlarsa (ve asyncio kullanmanın amacını bozarlar) ilk yer): urllib.request.urlopen` olay döngü engeller `çünkü

import asyncio 
import json 
import urllib.request 
from concurrent.futures import ThreadPoolExecutor 

zips = ['90210', '60647'] 
zip_cities = dict() 

@asyncio.coroutine 
def get_cities(zipcode,idx): 
    url = 'https://maps.googleapis.com/maps/api/geocode/json?key=abcdfg&address='+zipcode+'&sensor=true' 
    response = yield from loop.run_in_executor(executor, urllib.request.urlopen, url) 
    string = response.read().decode('utf-8') 
    data = json.loads(string) 
    print(data) 
    city = data['results'][0]['address_components'][1]['long_name'] 
    state = data['results'][0]['address_components'][3]['long_name'] 
    zip_cities.update({idx: [zipcode, city, state]}) 

if __name__ == "__main__": 
    executor = ThreadPoolExecutor(10) 
    loop = asyncio.get_event_loop() 
    tasks = [asyncio.async(get_cities(z, i)) for i, z in enumerate(zips)] 
    loop.run_until_complete(asyncio.wait(tasks)) 
    loop.close() 
    print(zip_cities) 
+0

Bunun için teşekkürler. 'Aiohttp' yolu çok daha temiz. –

+2

@ anthony-dandrea Posta kodu listeniz BÜYÜK olacaksa, aynı anda sayı eşzamanlı istekleri 100 bağlantı gibi bir sane numarasına sınırlamanızı da öneririm, aksi halde engellenebilirsiniz. * Deneyimden * konuşma * - –

3

Değil asyncio ile çok şey ama asyncio.get_event_loop() sen de besbelli işlev parametresi olarak gerekenlere değiştirip docs uyarınca asyncio.wait(tasks) kullanmak zorunda gerekenler olmalıdır: bende olmayan

zips = ['90210', '60647'] 
zip_cities = dict() 

@asyncio.coroutine 
def get_cities(zipcode): 
    url = 'https://maps.googleapis.com/maps/api/geocode/json?key=abcdefg&address='+zipcode+'&sensor=true' 
    fut = loop.run_in_executor(None,urllib.request.urlopen, url) 
    response = yield from fut 
    string = response.read().decode('utf-8') 
    data = json.loads(string) 
    city = data['results'][0]['address_components'][1]['long_name'] 
    state = data['results'][0]['address_components'][3]['long_name'] 
    zip_cities.update({idx: [zipcode, city, state]}) 

loop = asyncio.get_event_loop() 
tasks = [asyncio.async(get_cities(z, i)) for i, z in enumerate(zips)] 
loop.run_until_complete(asyncio.wait(tasks)) 
loop.close() 
print(zip_cities) # doesnt work 
{0: ['90210', 'Beverly Hills', 'California'], 1: ['60647', 'Chicago', 'Illinois']} 

>

@asyncio.coroutine 
def get_cities(zipcode): 
    url = 'https://maps.googleapis.com/maps/api/geocode/json?key=abcdefg&address='+zipcode+'&sensor=true' 
    fut = loop.run_in_executor(None,urllib.request.urlopen, url) 
    response = yield from fut 
    string = response.read().decode('utf-8') 
    data = json.loads(string) 
    city = data['results'][0]['address_components'][1]['long_name'] 
    state = data['results'][0]['address_components'][3]['long_name'] 
    return [zipcode, city, state] 

loop = asyncio.get_event_loop() 
tasks = [asyncio.async(get_cities(z)) for z in zips] 
loop.run_until_complete(asyncio.wait(tasks)) 
loop.close() 
zip_cities = {i:tsk.result() for i,tsk in enumerate(tasks)} 
print(zip_cities) 
{0: ['90210', 'Beverly Hills', 'California'], 1: ['60647', 'Chicago', 'Illinois']} 
: = 3.4.4 yüzden yerine asyncio.ensure_future

ait asyncio.async kullanmak Veya mantığını değiştirmek ve görevleri task.result gelen dicti oluşturmak zorunda

Dış modülleri arıyorsanız, asyncio ile çalışan bir port of requests da var.

+1

Bu, hala senkron olduğunu. – dano

+0

@dano, evet, çalışmayı başarmak için büyük bir değişiklik olmadığını göz ardı ettim. –

İlgili konular