Luận bàn về: Generators, Coroutines, Native Coroutines và async/await trong python!

Python

Note:
Bài viết giới thiệu và thảo luận về một số chức năng từ phiên bản python 3.4. Và chức năng native coroutines, async/await syntax thì mới xuất hiện trên phiên bản 3.5. Bởi vậy mà bạn hãy chịu khó cập nhật phiên bản mới nhất tại thời điểm mình viết bài này là 3.5.1 để trải nghiệm nhé.

Generators

Generators là một hàm dùng cho việc tạo ra các dữ liệu. Như các bạn biết, một hàm thì thường trả về dữ liệu và sau đó được hủy khi kết thúc. Nếu chúng ta gọi lại, hàm đó sẽ thực thi lại từ đầu. Nhưng generator có thể yield một gía trị và dừng việc thực thi của hàm đó lại. Việc kiểm soát được trả lại cho phạm vi gọi hàm. Sau đó chúng ta lại có thể tiếp tục gọi lại hàm nếu muốn và sẽ nhận lại một gía trị khác nếu có. Hãy cùng làm một ví dụ nhỏ dưới đây để hiểu rõ thêm về generator.

def simple_gen():
    yield "Hello"
    yield "World"
gen = simple_gen()
print("First: ", next(gen))
print("Second: ", next(gen))

Kết qủa:

First: Hello
Second: World

Lưu ý: Generator thì không trả về bất kỳ một gía trị nào nhưng nó có thể trả về một generator object giống như việc sử dụng interable. Vì vậy chúng ta có thể sử dụng next() trong generator object để lặp qua các gía trị. Hoặc đơn giản hơn bạn có thể dùng vòng lặp for.

Đọc đến đây chắc bạn đã hiểu được cách generator hoạt động đúng không. Nhưng vấn đề là làm sao sử dụng generator một cách hữu ích. Nếu sếp yêu cầu bạn hãy viết một hàm in ra các số từ 1 đến 100, cách đơn giản nhất ai cũng nghĩ tới có lẽ là sử dụng ***range()***. Thực chất bạn tạo ra một list rỗng, thêm các số vào list đó và lại trả lại chúng. Nhưng có chút thay đổi, sếp yêu cầu bạn hãy in ra 10 triệu số. Nếu lưu chúng trong một list thì bạn sẽ bị tràn bộ nhớ (run out of memory). Và giờ là lúc bạn cần sử dụng generator, hãy cùng làm một ví dụ sau, vấn đề của bạn sẽ được giải quyết nhanh chóng.

def generate_num():
    num = 0
    white True:
        yield num
        num = num + 1
        
nums = generate_num()
for x in nums():
    print(x)
    if x > 4:
        break

Kết qủa:

0
1
2
3
4
5

Phù yêu cầu khó của sếp tạm thời đã được giải quyết. Băn khoăn không biết sếp có bảo in ra hàng tỉ không nữa ^^.

Tổng kết: Một generator là một hàm có thể tạm dừng việc thực thi và tạo ra nhiều gía trị thay vì chỉ tạo ra một gía trị. Một generator có thể trả về một gía trị hoặc một generator object giống với interable.

Coroutines

Bạn đã biết cách sử dụng generator. Ta có thể lấy dữ liệu từ một hàm và tạm dừng thực thi hàm đó. Vây giờ nếu muốn truyền dữ liệu vào hàm đó thì ta sẽ làm cách nào. Coroutines là cái mà ta nghĩ tới. Từ khóa yield dùng để đẩy dữ liệu và cũng là để tạm dừng thực thi hàm. Chúng ta sẽ dừng phương thức *send() để gửi dữ liệu trở lại. Nó có thể được gọi là “generator based coroutines”. Hãy xem ví dụ dưới đây.

def coro():
    hello = yield "Hello"
    yield hello
    
c = coro()
print(next(c))
print(c.send("World"))

Kết qủa:

Hello
World

Chúng ta sử dụng hàm next() để lấy dữ liệu bình thường, dữ liệu trả về “Hello”. Và truyền dữ liệu trở lại bằng việc sử dụng hàm send() để truyền dữ liệu trở lại và nhận dữ liệu trả về từ hàm send() “World”.

**Async I/O and the asyncio module **

Từ phiên bản Python 3.4 trở đi. Python đã cung cấp những API hỗ trợ rất tốt cho việc lập trình async nói chung. Chúng ta có thể sử dụng coroutines và module asyncio để dễ dang hơn cho việc lập trình async io. Cùng mình xem một ví dụ nhỏ dưới đây để hiểu thêm về async:

import asyncio
import datetime
import random
 
 
@asyncio.coroutine
def display_date(num, loop):
    end_time = loop.time() + 50.0
    while True:
        print("Loop: {} Time: {}".format(num, datetime.datetime.now()))
        if (loop.time() + 1.0) >= end_time:
            break
        yield from asyncio.sleep(random.randint(0, 5))
 
 
loop = asyncio.get_event_loop()
 
asyncio.ensure_future(display_date(1, loop))
asyncio.ensure_future(display_date(2, loop))
 
loop.run_forever()

Kết qủa

Loop: 1 Time: 2015-12-28 08:26:19.202881
Loop: 2 Time: 2015-12-28 08:26:19.202951
Loop: 1 Time: 2015-12-28 08:26:19.203021
Loop: 1 Time: 2015-12-28 08:26:19.203064
Loop: 2 Time: 2015-12-28 08:26:21.205266
Loop: 2 Time: 2015-12-28 08:26:22.206516
Loop: 1 Time: 2015-12-28 08:26:24.205760
Loop: 1 Time: 2015-12-28 08:26:26.208017
Loop: 2 Time: 2015-12-28 08:26:26.208101
Loop: 1 Time: 2015-12-28 08:26:26.208162
Loop: 2 Time: 2015-12-28 08:26:27.209357
...

Đoạn code trên gần như đã giải thích việc nó làm. Chúng ta tạo môt coroutine display_date(num, loop) dùng để định danh một số cùng một sự kiện lặp và tiếp tục in thời gian hiện tại ra màn hình. Bạn có để ý đến từ khóa yield from, chúng ta dùng nó để đợi kết qủa trả về từ việc gọi hàm ***asyncio.sleep()***. Các hàm này sẽ trả về gía trị sau một vài giây, vì vậy mà chúng ta sẽ sinh một số nguyên ngẫu nhiên cho hàm asyncio.sleep. Tiếp đó là việc gọi hàm asyncio.ensure_future() để thực thi một coroutine trong vòng lặp mặc định và gọi hàm loop.run_forever() để vòng lặp sẽ chạy mãi mãi.

Nhìn vào output. Chúng ta thấy rằng hai vòng lặp được thực hiện đồng thời. Khi mà bạn gọi yield from, các vòng lặp hiểu rằng nó có cần làm việc trong một thời gian, bởi vậy các sự kiện khác sẽ tạm dừng và chạy vào thời điểm khác. Như vậy bạn có thể thấy, tuy hai sự kiện này chạy đồng thời nhưng không song song, mỗi một sự kiện vòng lặp đều chạy đơn luồng.đều

**Native Coroutines and async/await **

Trong phiên bản Python 3.5 có một chút thay đổi. Bạn có thể sử dụng async / await thay cho việc sử dụng *** yield from ***. Bạn nào chắc từng phan cuồng .Net một thời như mình hẳn nhìn async / await chắc sẽ nhớ ngay cách viết bên .Net ^^. Cùng mình xem cách viết nhé:

import asyncio
import datetime
import random
 
 
async def display_date(num, loop, ):
    end_time = loop.time() + 50.0
    while True:
        print("Loop: {} Time: {}".format(num, datetime.datetime.now()))
        if (loop.time() + 1.0) >= end_time:
            break
        await asyncio.sleep(random.randint(0, 5))
 
 
loop = asyncio.get_event_loop()
 
asyncio.ensure_future(display_date(1, loop))
asyncio.ensure_future(display_date(2, loop))
 
loop.run_forever()

Đoạn code trên, từ khóa def để khai báo hàm đã được thay bằng async và yield from được thay bằng await.

**Native vs Generator Based Coroutines: Interoperability **

Bạn có thể thấy giữa Native and Generator thì không có sự khác biệt ngoại trừ cú pháp, cách viết. Vì vậy mà chúng ta không thể dùng await trong một Generator Based Coroutine hoặc yield / yield from trong một Navtive

Mặc dù có sự khác biệt, ta có thể giúp chúng tương thích với bằng. Chúng ta sử dụng decorator @types.coroutine … Xem ví dụ dưới đây để bạn hiểu thêm cách làm việc của nó nhé.

import asyncio
import datetime
import random
import types
 
 
@types.coroutine
def my_sleep_func():
    yield from asyncio.sleep(random.randint(0, 5))
 
 
async def display_date(num, loop, ):
    end_time = loop.time() + 50.0
    while True:
        print("Loop: {} Time: {}".format(num, datetime.datetime.now()))
        if (loop.time() + 1.0) >= end_time:
            break
        await my_sleep_func()
 
 
loop = asyncio.get_event_loop()
 
asyncio.ensure_future(display_date(1, loop))
asyncio.ensure_future(display_date(2, loop))
 
loop.run_forever()

**Bài viết cũng đã dài rồi, mình xin kết thúc ở đây. Rất hi vọng nó có ích với một số bạn :slight_smile: **

Nguồn: Kipalog.com

7 Likes

Lần sau bác không cần copy qua đâu, cứ dẫn link xong để category là hackernews là được :smile: làm thế này lấy mất view của anh em bên đó :smile:

1 Like
83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?