2017-08-26 66 views
25

Mam Connection obiekt, który jest używany do przechowywania odczytu i zapisu strumieni asyncio połączeń:Jaki jest prawidłowy sposób uzyskiwania ze strumienia?

class Connection(object): 

    def __init__(self, stream_in, stream_out): 
     object.__init__(self) 

     self.__in = stream_in 
     self.__out = stream_out 

    def read(self, n_bytes : int = -1): 
     return self.__in.read(n_bytes) 

    def write(self, bytes_ : bytes): 
     self.__out.write(bytes_) 
     yield from self.__out.drain() 

Po stronie serwera, connected tworzy Connection obiektowi każdym razem, gdy klient łączy się, a następnie czyta 4 bajty.

@asyncio.coroutine 
def new_conection(stream_in, stream_out): 
    conn = Connection(stream_in, stream_out) 
    data = yield from conn.read(4) 
    print(data) 

Po stronie klienta wypisane są 4 bajty.

@asyncio.coroutine 
def client(loop): 
    ... 
    conn = Connection(stream_in, stream_out) 
    yield from conn.write(b'test') 

to działa prawie jak oczekiwano, ale muszę yield from każdy read i write rozmowy. Próbowałem yield from ing od wewnątrz Connection:

def read(self, n_bytes : int = -1): 
    data = yield from self.__in.read(n_bytes) 
    return data 

Ale zamiast pobierania danych, mam wyjścia jak

<generator object StreamReader.read at 0x1109983b8> 

Jeśli zadzwonię read i write z wielu miejsc, bym nie preferowała powtórz yield from s za każdym razem; raczej trzymanie ich wewnątrz Connection. Moje ostatecznym celem jest wyciąć mój new_conection funkcji to:

@asyncio.coroutine 
def new_conection(stream_in, stream_out): 
    conn = Connection(stream_in, stream_out) 
    print(conn.read(4)) 
+0

Dlaczego musisz ustąpić? Jeśli nie zrezygnujesz z conn.read (4), wygląda na to, że po prostu zwraca obiekt bajtów. Czy tego właśnie szukasz? – RageCage

+0

@RageCage: Bez 'yield from'ing,' conn.read (4) 'nadal zwraca generator:' ' –

+0

Przepraszam, że powinienem był wyjaśnić; jeśli nie zrezygnujesz z pierwszej iteracji conn.read() (wersja z pojedynczą linią), jaki jest wynik? – RageCage

Odpowiedz

6

Ponieważ StreamReader.read is a coroutine, to jedyne opcje nazywając są: a) otoczenie go w Task lub Future i działa, że ​​za pomocą pętli zdarzeń b) await nia go z współprogram określonym z async def, albo c) za pomocą yield from z nią z coroutine zdefiniowany jako funkcja ozdobiona @asyncio.coroutine.

Od Connection.read jest wywoływana z pętli zdarzeń (przez współprogram new_connection), nie można ponownie użyć tej pętli zdarzeń uruchomić Task lub Future dla StreamReader.read: event loops can't be started while they're already running. Będziesz musiał albo stop the event loop (katastrofalne i prawdopodobnie nie można zrobić poprawnie) lub create a new event loop (niechlujny i pokonujący cel użycia coroutines). Żadne z nich nie jest pożądane, więc musi być funkcją coroutine lub async.

Pozostałe dwie opcje (await w async def współprogram lub yield from w @asyncio.coroutine -decorated funkcji) to w większości równoważne. Jedyna różnica polega na tym, że async def and await were added in Python 3.5, więc dla 3.4, używając yield from i @asyncio.coroutine jest jedyną opcją (coroutines i asyncio nie istniały przed 3.4, więc inne wersje są nieistotne). Osobiście wolę używać async def i await, ponieważ definiowanie coroutines z async def jest czystsze i czystsze niż w przypadku dekoratora.

W skrócie: mam Connection.read i new_connection być współprogram (stosując albo dekorator lub słowa kluczowego async) i używać await (lub yield from) podczas wywoływania innych współprogram (await conn.read(4) w new_connection i await self.__in.read(n_bytes) w Connection.read).

+1

Ah, bardzo ładna odpowiedź Mego! Jest to wyraźnie napisane przez kogoś, kto wie, o czym mówi. Dużo się nauczyłem czytając to. +1 –

1

znalazłem klocek StreamReader source code na linii 620 jest rzeczywiście doskonały przykład wykorzystania funkcji.

W poprzedniej odpowiedzi przeoczyłem fakt, że self.__in.read(n_bytes) jest nie tylko coroutine (co powinienem był wiedzieć biorąc pod uwagę, że był z modułu XD asyncio), ale daje wynik on-line. Więc w rzeczywistości jest to generator i trzeba z niego wydostać.

Pożyczanie tej pętli z kodem źródłowym, twoja funkcja odczytu powinien wyglądać mniej więcej tak:

def read(self, n_bytes : int = -1): 
    data = bytearray() #or whatever object you are looking for 
    while 1: 
     block = yield from self.__in.read(n_bytes) 
     if not block: 
      break 
     data += block 
    return data 

Ponieważ self.__in.read(n_bytes) jest generator, trzeba kontynuować, otrzymując od niego aż daje pusty rezultat do sygnalizowania koniec czytania. Teraz funkcja odczytu powinna raczej zwracać dane niż generator. Nie musisz tracić z tej wersji conn.read().

+0

Korzystając z funkcji dokładnie tak, jak ją dostarczyłeś, nadal otrzymuję obiekt generatora ('Connection.read'). –

+0

Nadal upadasz z połączenia conn.read? Spróbuj wydrukować dane i typ (dane) w funkcji odczytu, aby zobaczyć, co to jest przed powrotem. – RageCage

+0

Nie, usunąłem to i wypróbowałem 'data = conn.read (4)' zamiast tego. To generator. –