import time

def n_hello():
    for i in range(6):
        print(i)
    
    
def c_hello():
    for i in range(4):
        print('in function {}'.format(i))
        yield i

def infinit_loop():
    num = 0
    while True:
        num += 1
        print(num)
        yield

    
n_hello()
print("=====")
c = c_hello()
next(c)
print("come back to main")
next(c)
next(c)

"""
Explanation: python async programming
非同步編程在python中最近是越來越受歡迎，在python中有著許多libraries是用來做非同步的，其中之一是asyncio而且這也是讓python在async編程受歡迎的主因，在開始正題前，我們先來理解一些歷史緣由。
在普遍的程式，執行順序都是一行一行執行，每次要繼續往下執行前，都會等著上一行完成，也就是俗稱的Sequential programming，那麼這樣的編程可能會遇到什麼問題呢？ 最大的問題就是如果上一行執行太久的話，我一定要等上一行執行完我才能夠繼續往下走嗎？ 最常見的情況就是api request，得到回傳結果，我才能繼續往下走，但是其實我下面接著要做的並不用等這個結果就可以執行了，所以就會耗費無意義的時間，為了解決這樣的事情，會使用thread。
process 可以產生多個 thread，可以讓你的程式一次做很多事情，把它想成影分身，主體只有一個，但是你的分身卻可以同時幫你做其他事情。

上面這張圖，說明了什麼？ 帥！ 哈哈，其實是想表達，鳴人自己(process)，開出了很多分身(thread)，每個分身都做不同的事情
方便吧！ 但是 thread 是有他的問題存在的，其中像是

race condition
dead lock
resource starvavtion

先撇除上面會遇到的問題，thread還有著一個成本就是cpu的context switch，因為一顆cpu一次只能run一個thread，它實際上背後用很快的速度在進行thread的交換並執行，這就是所謂的context switch。那麼會有既可以達到多工的效果，又可以免除遇到上述的race condition等等問題的技術存在嗎？！ 答案是有的，那就是今天我們要講的主題 python async io，ayncio背後其實是用到coroutine的概念實作，從wiki上面來看，其實coroutine就是一種可以中斷及繼續執行函式呼叫的技術，直接從下面的例子來看！
End of explanation
"""


def g(x):
    for i in range(x):
        yield i
    
def will_cause_exception():
    x = yield
    print("wow {}".format(x))
    return x


def infinite_send():
    while True:
        x = yield
        print("send {}".format(x))
        


w = will_cause_exception()
next(w)
try:
    w.send(5)
except StopIteration as e:
    
    return_value = e.value
    # the function return value will be store in the exception's value
    print(return_value)
    # let you see exception
    raise e



"""
Explanation: 上面就是python最基本支援coroutine的使用方式，第一個function n_hello 是一般的for loop版本的印出數字，另外一個function c_hello 是使用yield，藉此讓你看看兩者行為，明顯的感受出使用yield可以將程式的執行順序從subroutine轉回到main，繼續呼叫next又可以跳回去subroutine。
有沒有覺得跟multi thread很像呢，基本上是行為是差不多的，但是coroutine是基於中斷函式，繼續執行其他函式的方式來達到多工，並不像multi thread，會有同時兩個thread執行同份程式碼的問題，進而造成前面所說的，race condition, dead lock.. 那些問題，前面使用鳴人的影分身來比喻multi thread，對於coroutine，我個人想要使用下面這張來比喻

影子模仿術，鹿丸放出多條影子(coroutine)，藉由自己的大腦來控制所有人的行動。
那麼接著再稍微深入看看yield的使用方式，前面使用方式是yield把值從function傳出去，那麼我們今天可以把值從外面傳到function裡面使用嗎？ 答案是可以的！ 以下看看例子
End of explanation
"""


def test_yield_from():
    w = will_cause_exception()
    value = yield from w
    print("no exception {}".format(value))
    yield
    
t = test_yield_from()
next(t)
t.send(10)



def amazing_yeild_from(x):
    yield from range(x)
    yield from range(x-1, -1, -1)
    
print(list(amazing_yeild_from(5)))



%%time
import asyncio
import requests

@asyncio.coroutine
def aio_requests(url):
    r = requests.get(url)
    return r

@asyncio.coroutine
def aio_response(response):
    data = response.text
    return data

urls = ['http://www.google.com', 'http://www.yandex.ru',
        'http://www.python.org', 'http://www.python.org', 'http://www.python.org']

@asyncio.coroutine
def call_url(url):
    response = yield from aio_requests(url)
    data = yield from aio_response(response)
    print('{}: {} bytes'.format(url, len(data)))
    return data

futures = [call_url(url) for url in urls]

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(futures))

%%time
def syn_call_url(url):
    r = requests.get(url)
    data = r.text
    print('{}: {} bytes'.format(url, len(data)))
    
for url in urls:
    syn_call_url(url)
    
    

%%time
async def async_requests(url):
    r = requests.get(url)
    return r

async def async_response(response):
    data = response.text
    return data

async def call_url(url):
    response = await async_requests(url)
    data = await async_response(response)
    print('{}: {} bytes'.format(url, len(data)))
    return data

futures = [call_url(url) for url in urls]

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(futures))

"""
Explanation: 根據上面的使用情境，你應該會覺得多多少少可以有更方便的用法才對，因此python的確在pep380有提出yield from這個語法糖
End of explanation
"""


%%time

import time

time.sleep(1)

"""
Explanation: syntax sugar

@asyncio.coroutine => async
yield from => await

asyncio vs thread
asyncio 神秘在哪？ 讓我們來瞧瞧
https://www.reddit.com/r/learnpython/comments/5qwm5h/asyncio_for_dummies/dd432ke/
golang 沒有 reentrant lock
End of explanation
"""


