iT邦幫忙

2

PHP yield的使用情境

php
Wen. 2020-07-03 18:04:371734 瀏覽
  • 分享至 

  • xImage

今天在跟著ReccaChao大的鐵人文章學習([Day 15] 終於講到 6.0 的改變了!來看 Laravel LazyCollection),發現到一個沒看過的語法[yield]、[Generator],馬上丟去餵狗。

先說說我對他倆的理解好了,yield必須在function內作用,當使用他接值時不需要return,yield會幫你處理好,且記憶體還不會被吃住,當使用此function時,就可以使用PHP內建Generator物件的一些method操作,這個物件操作還蠻像陣列的。

用yield只是省了記憶體,後續還是會把資料做處理,所以記憶體只是省在初始建立資料時,我還是習慣操作陣列,而且普遍很少看到有人使用yield(可能是我見識淺薄)。

以上是我的初步了解,如果以上理解有誤,歡迎提出。

想請教各位大大們,常用yield嗎 ? 通常會在哪個使用情境下使用呢?

看更多先前的討論...收起先前的討論...
像是csv檔案匯入匯出可以應用,主要是沒有記憶體的消耗問題
Wen. iT邦新手 5 級 ‧ 2020-07-03 18:26:50 檢舉
普遍看到的範例都是對於檔案的操作,為何很少人用他來取代對於陣列的操作呢?
常見的從資料庫取出資料時,應該也可以用此方法存資料吧?
甲土豆 iT邦新手 5 級 ‧ 2020-07-06 09:29:58 檢舉
大多數人使用情境是:大量數據時候會用到,如果直接使用 array 的話大量資料存入記憶體,很容易導致記憶體溢出,所以PHP 提供此生成器方法,進行批量處裡大數據
Wen. iT邦新手 5 級 ‧ 2020-07-09 20:47:41 檢舉
了解,感謝回覆
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 個回答

5
froce
iT邦大師 1 級 ‧ 2020-07-03 22:15:41

PHP我不熟,不過跟python的yield應該是一樣的東西。

我們來看一下下面2個費氏函數(你把python的list當成php的array就行):

import sys

def fibY(n):
    i, a, b = 1, 1, 1
    while i <= n:
        if i <= 2:
            yield 1
        else:
            a, b = b, a+b
            yield b
        i += 1


def fibL(n):
    res = []
    i, a, b = 1, 1, 1
    while i <= n:
        if i <= 2:
            res.append(1)	
        else:
            res.append(sum(res[-2:])) #最後2位的總和
        i += 1
    return res  # 傳回array

# 觀察記憶體佔用
fL = fibL(10000)
fY = fibY(10000)
fL2 = fibL(10)
fY2 = fibY(10)

print(type(fL), sys.getsizeof(fL))      # <class 'list'> 87624
print(type(fL2), sys.getsizeof(fL2))    # <class 'list'> 192

print(type(fY), sys.getsizeof(fY))      # <class 'generator'> 120
print(type(fY2), sys.getsizeof(fY2))    # <class 'generator'> 120
print(sys.getsizeof(list(fY)))          # 83112 (將10000個費氏數列結果存入list中佔用之記憶體,可以看到和fL的大小很接近)

# list和generator兩者都是可迭代物件,所以都可以靠for來遍歷
for i in fL2:
    print(i)

for i in fY2:
    print(i)
    
print(len(fL2))    # 10
print(len(fY2))    # TypeError: object of type 'generator' has no len()
# 生成器沒有長度(因為他除非計算完成並將結果都儲存,要不然沒人知道他多長,除非你自己定義)

雖然兩者都可以迭代,但是傳回array的時候,作業系統一定得先分配一塊記憶體給結果然後再去迭代,所以如果你取越多個,佔用記憶體越大。
但生成器因為是惰性取值,到yield時才傳出計算結果,所以取10000個和10個佔用的記憶體是一樣的。
當然你會覺得2者行為很像,但是這內部表現不一樣你可以用的地方就不一樣了。

如果你的老闆今天跟你說我要取 1000000000000000000 個費氏數列並且產生檔案,用list的話你得跟他申請一台記憶體夠存 1000000000000000000 個的電腦,但是用yield你可以先把每次的計算結果存到硬碟就好。

yield版的理論上不管你n要多大,只要你電腦的記憶體能夠容納的下第n個的數列元素的大小就行。
list版的你得容納所有前n個元素總和。

你可以理解成yield版的是你跟電腦說:你把結果一個一個告訴我。
list版的是:你一次把結果告訴我。

所以你的理解沒錯,sql的結果很適合用yield來處理。
實際上sql本身也是用類似的惰性取值來處理。
所以cursor幾乎都有next()

Wen. iT邦新手 5 級 ‧ 2020-07-03 22:54:10 檢舉

感謝大大範例,感覺yield可以應用在很多地方,但是卻又很少人用,php來說,我猜是語言特性吧,記憶體不像某些語言吃得兇,而且大家還是習慣array的操作,相對來得簡單吧!所以較少人用吧,我也是少用好了,怕公司接收維護的人看不懂@@

froce iT邦大師 1 級 ‧ 2020-07-03 23:25:35 檢舉

php來說,我猜是語言特性吧,記憶體不像某些語言吃得兇

不是吧,只是生成器的觀念比較難理解,通常是在array這些基本資料結構之後才會介紹到。
而且實例上生成器的好處不一定能體現出來,除非你必須很節省記憶體,或是有像上面例子的這種必須緩存的需求,要不然沒一定要用生成器。

更不用說生成器你如果要得到所有結果你還是得把他存入array,在有些簡單程式裡不太直覺。

除非像是python這種積極把生成器應用在語法的語言,要不然會有這種不太被重視的結果很正常。

Wen. iT邦新手 5 級 ‧ 2020-07-04 08:58:12 檢舉

真的,生成器的概念,第一次看到的時候愣住了,這是什麼鬼,不用return就接好值還直接轉物件,像陣列 卻又不能用count取數,顛覆想像,哈哈哈。
感謝回覆!

我要發表回答

立即登入回答