2015-05-10 15 views
12

İsteğe bağlı olarak, kendi yöntemleri ve işlevleri, coroutines değil gibi kullanabilmek için son kullanıcıları istiyorum bir kitaplık yazıyorum. Bu fonksiyon verilen Örneğinİsteğe bağlı olarak normal işlevler olarak işlev gören asyncio coroutines'i nasıl yazabilirim?

:

>>> response = asyncio.get_event_loop().run_until_complete(blah_getter()) 
:

@asyncio.coroutine 
def blah_getter(): 
    return (yield from http_client.get('http://blahblahblah')) 

kendi kodlarında herhangi asenkron özellikleri kullanmak için umursamıyor Bir son kullanıcı, hala asyncio ithalat ve bu çalıştırmak zorundadır

Yapabilseydim, blah_getter'un içinde koroutin olarak adlandırılıp çağrılmadığımı belirler ve buna göre tepki verir.

@asyncio.coroutine 
def async_gettter(): 
    return (yield from http_client.get('http://example.com')) 

def sync_getter() 
    return asyncio.get_event_loop().run_until_complete(async_getter()) 

magically_determine_if_being_yielded_from() aslında event_loop.is_running() ama şiddetle senkronizasyonu karıştırmak için tavsiye ve do not: -:

Yani böyle bir şey asenkron eşyordam ve senkron düzenli fonksiyonu

@asyncio.coroutine 
def blah_getter(): 
    if magically_determine_if_being_yielded_from(): 
     return (yield from http_client.get('http://blahblahblah')) 
    else: 
     el = asyncio.get_event_loop() 
     return el.run_until_complete(http_client.get('http://blahblahblah')) 
+0

İşlevinizi bir "blah_getter" stili işlevinde saran bir dekoratör yapabilirsiniz. –

+0

Ayrıca bkz. Https://stackoverflow.com/q/25299887/320911 –

cevap

19

Sen iki işlevi gerek aynı işlevdeki async kodu.

+4

Bu cevabı kabul ediyorum: ikinin karıştırılması kötü bir fikirdir ve karışıklığa ve beklenmedik sonuçlara yol açabilir. "Açık, örtük olmaktan iyidir." –

+2

Biraz daha düşündükten sonra haklı olduğunu düşünüyorum. Açık, örtük olmaktan iyidir! –

+0

Burada ilgili bir soru var: https://stackoverflow.com/questions/45213133/async-sync-async-calls-in-one-python-event-loop – dmitry

12

Andrew'un cevabına katılıyorum, sadece üst düzey işlevler yerine, nesnelerle uğraşırken, eşzamansız yöntemlerin eşzamanlı sürümlerini otomatik olarak eklemek için bir metaclass kullanabilirsiniz. Bu örneğe bakın:

import asyncio 
import aiohttp 

class SyncAdder(type): 
    """ A metaclass which adds synchronous version of coroutines. 

    This metaclass finds all coroutine functions defined on a class 
    and adds a synchronous version with a '_s' suffix appended to the 
    original function name. 

    """ 
    def __new__(cls, clsname, bases, dct, **kwargs): 
     new_dct = {} 
     for name,val in dct.items(): 
      # Make a sync version of all coroutine functions 
      if asyncio.iscoroutinefunction(val): 
       meth = cls.sync_maker(name) 
       syncname = '{}_s'.format(name) 
       meth.__name__ = syncname 
       meth.__qualname__ = '{}.{}'.format(clsname, syncname) 
       new_dct[syncname] = meth 
     dct.update(new_dct) 
     return super().__new__(cls, clsname, bases, dct) 

    @staticmethod 
    def sync_maker(func): 
     def sync_func(self, *args, **kwargs): 
      meth = getattr(self, func) 
      return asyncio.get_event_loop().run_until_complete(meth(*args, **kwargs)) 
     return sync_func 

class Stuff(metaclass=SyncAdder): 
    @asyncio.coroutine 
    def getter(self, url): 
     return (yield from aiohttp.request('GET', url)) 

Kullanımı:

>>> import aio, asyncio 
>>> aio.Stuff.getter_s 
<function Stuff.getter_s at 0x7f90459c2bf8> 
>>> aio.Stuff.getter 
<function Stuff.getter at 0x7f90459c2b70> 
>>> s = aio.Stuff() 
>>> s.getter_s('http://example.com') 
<ClientResponse(http://example.com) [200 OK]> 
<CIMultiDictProxy {'ACCEPT-RANGES': 'bytes', 'CACHE-CONTROL': 'max-age=604800', 'DATE': 'Mon, 11 May 2015 15:13:21 GMT', 'ETAG': '"359670651"', 'EXPIRES': 'Mon, 18 May 2015 15:13:21 GMT', 'SERVER': 'ECS (ewr/15BD)', 'X-CACHE': 'HIT', 'X-EC-CUSTOM-ERROR': '1', 'CONTENT-LENGTH': '1270', 'CONTENT-TYPE': 'text/html', 'LAST-MODIFIED': 'Fri, 09 Aug 2013 23:54:35 GMT', 'VIA': '1.1 xyz.com:80', 'CONNECTION': 'keep-alive'}> 
>>> asyncio.get_event_loop().run_until_complete(s.getter('http://example.com')) 
<ClientResponse(http://example.com) [200 OK]> 
<CIMultiDictProxy {'ACCEPT-RANGES': 'bytes', 'CACHE-CONTROL': 'max-age=604800', 'DATE': 'Mon, 11 May 2015 15:25:09 GMT', 'ETAG': '"359670651"', 'EXPIRES': 'Mon, 18 May 2015 15:25:09 GMT', 'SERVER': 'ECS (ewr/15BD)', 'X-CACHE': 'HIT', 'X-EC-CUSTOM-ERROR': '1', 'CONTENT-LENGTH': '1270', 'CONTENT-TYPE': 'text/html', 'LAST-MODIFIED': 'Fri, 09 Aug 2013 23:54:35 GMT', 'VIA': '1.1 xys.com:80', 'CONNECTION': 'keep-alive'}> 
+1

Harika zaman tasarrufu! Bunu paylaştığın için teşekkürler. –

+0

sync_maker functools.decorator kullanmalıdır. –

+0

@MatthiasUrlichs - bu nasıl functools.decorator ile uygulanacak? – wesm

0

Ayrıca size fonksiyon senkron yapar basit bir dekoratör oluşturabilir. Bu yaklaşım global fonksiyonlara ve metotlara uygulanabilir.

Örnek.

# the decorator 
def sync(f): 
    ASYNC_KEY = 'async' 

    def f_in(*args, **kwargs): 
     if ASYNC_KEY in kwargs: 
      async = kwargs.get(ASYNC_KEY) 
      del kwargs[ASYNC_KEY] 
     else: 
      async = True 

     if async: 
      return f(*args, **kwargs)   
     else: 
      return asyncio.get_event_loop().run_until_complete(f()) 

    return f_in 

# below: the usage  
@sync 
async def test(): 
    print('In sleep...') 
    await asyncio.sleep(1) 
    print('After sleep')  


# below: or asyncio.get_event_loop().create_task(test()) 
asyncio.get_event_loop().run_until_complete(test()) 
# and here is your syncronious version 
test(async=False) 

Üstelik: muhtemelen değil her yöntem çağrısı async geçmek özel sarıcı sınıf oluşturmak için duygusu var. Örnek aşağıda.

class SyncCallerWrapper(object): 
    def __init__(self, obj, is_async=True): 
     self._obj = obj 
     self._is_async = is_async 


    def __getattr__(self, name): 
     def sync_wrapper(obj_attr): 
      def f(*args, **kwargs): 
       return asyncio.get_event_loop().run_until_complete(obj_attr(*args, **kwargs)) 

      return f 

     obj_attr = getattr(self._obj, name) 

     if not self._is_async and asyncio.iscoroutinefunction(obj_attr): 
      return sync_wrapper(obj_attr)   

     return obj_attr 


class C(object): 
    async def sleep1(self): 
     print('In sleep1...') 
     await asyncio.sleep(1) 
     print('After sleep1') 


    async def sleep2(self): 
     print('In sleep2...') 
     await asyncio.sleep(1) 
     print('After sleep2')  


# you don't want any concurrency in your code 
c_sync = SyncCallerWrapper(C(), is_async=False) 
c_sync.sleep1() 
c_sync.sleep2() 

# here you want concurrency: class methods are coroutines 
c_async = SyncCallerWrapper(C(), is_async=True) 
asyncio.get_event_loop().run_until_complete(c_async.sleep1()) 
asyncio.get_event_loop().run_until_complete(c_async.sleep2()) 

Daha zarif olmak için sınıfınızı bir işlevle (global kurucu) değiştirebilirsiniz. Daha sonra bir kullanıcı, is_async parametresini geçirerek C sınıfını oluşturabilir ve istenen davranışı oluşturabilir: yöntemler normal olarak (is_async=False) veya async işlevleri (is_async=True) olarak işlev görür.

def C(*args, **kwargs): 
    KEY_ISASYNC = 'is_async' 
    if KEY_ISASYNC in kwargs: 
     is_async = kwargs.get(KEY_ISASYNC) 
     del kwargs[KEY_ISASYNC] 
    else: 
     is_async = False 
    return SyncCallerWrapper(_C(*args, **kwargs), is_async=is_async) 

# you don't want any concurrency in your code 
c_sync = C(is_async=False) 
c_sync.sleep1() 
c_sync.sleep2() 

# here you want concurrency: class methods are coroutines 
c_async = C(is_async=True) 
asyncio.get_event_loop().run_until_complete(c_async.sleep1()) 
asyncio.get_event_loop().run_until_complete(c_async.sleep2()) 
İlgili konular