PHP的學習--生成器Generators

来源:http://www.cnblogs.com/CraryPrimitiveMan/archive/2016/01/14/5130056.html
-Advertisement-
Play Games

生成器提供了一種更容易的方法來實現簡單的對象迭代,相比較定義類實現 Iterator 介面的方式,性能開銷和複雜性大大降低。


生成器總覽

(PHP 5 >= 5.5.0, PHP 7)

生成器提供了一種更容易的方法來實現簡單的對象迭代,相比較定義類實現 Iterator 介面的方式,性能開銷和複雜性大大降低。

生成器允許你在 foreach 代碼塊中寫代碼來迭代一組數據而不需要在記憶體中創建一個數組, 那會使你的記憶體達到上限,或者會占據可觀的處理時間。相反,你可以寫一個生成器函數,就像一個普通的自定義函數一樣, 和普通函數只返回一次不同的是, 生成器可以根據需要 yield 多次,以便生成需要迭代的值。

一個簡單的例子就是使用生成器來重新實現 range() 函數。 標準的 range() 函數需要在記憶體中生成一個數組包含每一個在它範圍內的值,然後返回該數組, 結果就是會產生多個很大的數組。 比如,調用 range(0, 1000000) 將導致記憶體占用超過 100 MB。

做為一種替代方法, 我們可以實現一個 xrange() 生成器, 只需要足夠的記憶體來創建 Iterator 對象併在內部跟蹤生成器的當前狀態,這樣只需要不到1K位元組的記憶體。

Example #1 將 range() 實現為生成器

<?php
function xrange($start, $limit, $step = 1) {
    if ($start < $limit) {
        if ($step <= 0) {
            throw new LogicException('Step must be +ve');
        }

        for ($i = $start; $i <= $limit; $i += $step) {
            yield $i;
        }
    } else {
        if ($step >= 0) {
            throw new LogicException('Step must be -ve');
        }

        for ($i = $start; $i >= $limit; $i += $step) {
            yield $i;
        }
    }
}

/* 
 * 註意下麵range()和xrange()輸出的結果是一樣的。
 */

echo 'Single digit odd numbers from range():  ';
foreach (range(1, 9, 2) as $number) {
    echo "$number ";
}
echo "\n";

echo 'Single digit odd numbers from xrange(): ';
foreach (xrange(1, 9, 2) as $number) {
    echo "$number ";
}
?>

以上常式會輸出:

Single digit odd numbers from range():  1 3 5 7 9 
Single digit odd numbers from xrange(): 1 3 5 7 9 

Generator objects

When a generator function is called for the first time, an object of the internal Generator class is returned. This object implements the Iterator interface in much the same way as a forward-only iterator object would, and provides methods that can be called to manipulate the state of the generator, including sending values to and returning values from it.

 

生成器語法

一個生成器函數看起來像一個普通的函數,不同的是普通函數返回一個值,而一個生成器可以 yield 生成許多它所需要的值。

當一個生成器被調用的時候,它返回一個可以被遍歷的對象.當你遍歷這個對象的時候(例如通過一個foreach迴圈),PHP 將會在每次需要值的時候調用生成器函數,併在產生一個值之後保存生成器的狀態,這樣它就可以在需要產生下一個值的時候恢復調用狀態。

一旦不再需要產生更多的值,生成器函數可以簡單退出,而調用生成器的代碼還可以繼續執行,就像一個數組已經被遍歷完了。

Note:

一個生成器不可以返回值: 這樣做會產生一個編譯錯誤。然而return空是一個有效的語法並且它將會終止生成器繼續執行。

yield關鍵字

生成器函數的核心是yield關鍵字。它最簡單的調用形式看起來像一個return申明,不同之處在於普通return會返回值並終止函數的執行,而yield會返回一個值給迴圈調用此生成器的代碼並且只是暫停執行生成器函數。

Example #1 一個簡單的生成值的例子

<?php
function gen_one_to_three() {
    for ($i = 1; $i <= 3; $i++) {
        //註意變數$i的值在不同的yield之間是保持傳遞的。
        yield $i;
    }
}

$generator = gen_one_to_three();
foreach ($generator as $value) {
    echo "$value\n";
}
?>

以上常式會輸出:

1
2
3

Note:

在內部會為生成的值配對連續的整型索引,就像一個非關聯的數組。

如果在一個表達式上下文(例如在一個賦值表達式的右側)中使用yield,你必須使用圓括弧把yield申明包圍起來。 例如這樣是有效的:

$data = (yield $value);

而這樣就不合法,並且在PHP5中會產生一個編譯錯誤:

$data = yield $value;

The parenthetical restrictions do not apply in PHP 7.

這個語法可以和生成器對象的Generator::send()方法配合使用。

指定鍵名來生成值

PHP的數組支持關聯鍵值對數組,生成器也一樣支持。所以除了生成簡單的值,你也可以在生成值的時候指定鍵名。

如下所示,生成一個鍵值對與定義一個關聯數組十分相似。

Example #2 生成一個鍵值對

<?php
/* 
 * 下麵每一行是用分號分割的欄位組合,第一個欄位將被用作鍵名。
 */

$input = <<<'EOF'
1;PHP;Likes dollar signs
2;Python;Likes whitespace
3;Ruby;Likes blocks
EOF;

function input_parser($input) {
    foreach (explode("\n", $input) as $line) {
        $fields = explode(';', $line);
        $id = array_shift($fields);

        yield $id => $fields;
    }
}

foreach (input_parser($input) as $id => $fields) {
    echo "$id:\n";
    echo "    $fields[0]\n";
    echo "    $fields[1]\n";
}
?>

以上常式會輸出:

1:
    PHP
    Likes dollar signs
2:
    Python
    Likes whitespace
3:
    Ruby
    Likes blocks

和之前生成簡單值類型一樣,在一個表達式上下文中生成鍵值對也需要使用圓括弧進行包圍:

$data = (yield $key => $value);

生成null值

Yield可以在沒有參數傳入的情況下被調用來生成一個 NULL值並配對一個自動的鍵名。

Example #3 生成NULLs

<?php
function gen_three_nulls() {
    foreach (range(1, 3) as $i) {
        yield;
    }
}

var_dump(iterator_to_array(gen_three_nulls()));
?>

以上常式會輸出:

array(3) {
  [0]=>
  NULL
  [1]=>
  NULL
  [2]=>
  NULL
}

使用引用來生成值

生成函數可以像使用值一樣來使用引用生成。這個和returning references from functions(從函數返回一個引用)一樣:通過在函數名前面加一個引用符號。

Example #4 使用引用來生成值

<?php
function &gen_reference() {
    $value = 3;

    while ($value > 0) {
        yield $value;
    }
}

/* 
 * 我們可以在迴圈中修改$number的值,而生成器是使用的引用值來生成,所以gen_reference()內部的$value值也會跟著變化。
 */
foreach (gen_reference() as &$number) {
    echo (--$number).'... ';
}
?>

以上常式會輸出:

2... 1... 0... 

Generator delegation via yield from ¶

In PHP 7, generator delegation allows you to yield values from another generator, Traversable object, or array by using the yield from keyword. The outer generator will then yield all values from the inner generator, object, or array until that is no longer valid, after which execution will continue in the outer generator.

If a generator is used with yield from, the yield from expression will also return any value returned by the inner generator.

Example #5 Basic use of yield from

<?php
function count_to_ten() {
    yield 1;
    yield 2;
    yield from [3, 4];
    yield from new ArrayIterator([5, 6]);
    yield from seven_eight();
    yield 9;
    yield 10;
}

function seven_eight() {
    yield 7;
    yield from eight();
}

function eight() {
    yield 8;
}

foreach (count_to_ten() as $num) {
    echo "$num ";
}
?>

以上常式會輸出:

1 2 3 4 5 6 7 8 9 10 

Example #6 yield from and return values

<?php
function count_to_ten() {
    yield 1;
    yield 2;
    yield from [3, 4];
    yield from new ArrayIterator([5, 6]);
    yield from seven_eight();
    return yield from nine_ten();
}

function seven_eight() {
    yield 7;
    yield from eight();
}

function eight() {
    yield 8;
}

function nine_ten() {
    yield 9;
    return 10;
}

$gen = count_to_ten();
foreach ($gen as $num) {
    echo "$num ";
}
echo $gen->getReturn();
?>

以上常式會輸出:

1 2 3 4 5 6 7 8 9 10

Comparing generators with Iterator objects

The primary advantage of generators is their simplicity. Much less boilerplate code has to be written compared to implementing an Iterator class, and the code is generally much more readable. For example, the following function and class are equivalent:

<?php
function getLinesFromFile($fileName) {
    if (!$fileHandle = fopen($fileName, 'r')) {
        return;
    }
 
    while (false !== $line = fgets($fileHandle)) {
        yield $line;
    }
 
    fclose($fileHandle);
}

// versus...

class LineIterator implements Iterator {
    protected $fileHandle;
 
    protected $line;
    protected $i;
 
    public function __construct($fileName) {
        if (!$this->fileHandle = fopen($fileName, 'r')) {
            throw new RuntimeException('Couldn\'t open file "' . $fileName . '"');
        }
    }
 
    public function rewind() {
        fseek($this->fileHandle, 0);
        $this->line = fgets($this->fileHandle);
        $this->i = 0;
    }
 
    public function valid() {
        return false !== $this->line;
    }
 
    public function current() {
        return $this->line;
    }
 
    public function key() {
        return $this->i;
    }
 
    public function next() {
        if (false !== $this->line) {
            $this->line = fgets($this->fileHandle);
            $this->i++;
        }
    }
 
    public function __destruct() {
        fclose($this->fileHandle);
    }
}
?>

This flexibility does come at a cost, however: generators are forward-only iterators, and cannot be rewound once iteration has started. This also means that the same generator can't be iterated over multiple times: the generator will need to either be rebuilt by calling the generator function again, or cloned via the clone keyword.

 

摘自:http://php.net/manual/zh/language.generators.php

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一. DescriptionAttribute的普通使用方式1.1 使用示例 DescriptionAttribute特性可以用到很多地方,比較常見的就是枚舉,通過獲取枚舉上定義的描述信息在UI上顯示,一個簡單的枚舉定義:public enum EnumGender { ...
  • 一:文件查找1:文件檢索有時候我們因為改bug的需要,必須要知道這個MD5函數在哪些文件中用到了,然而不像cs中我們可以用shift+f12來查找下函數引用,這時候我們就可以用 “文件查找” 解決這個問題。我們可以在 “查找結果” 中清楚的看到哪些文件和哪些行使用到了這個md5函數,然後我們繼續順藤...
  • 首先、導入命名空間:using System.Net.Mail;定義發送電子郵件的方法[網上很多不同的,可以對比著看一下,WinForm的也適用]:/// /// 發送電子郵件/// /// 發件人郵箱地址/// 收件人郵箱地址/// 郵件主題/// 郵件內容/// public bool Send...
  • 1、查找空節點//*[not(text())] 表示內容為空的節點//*[count(*)=0] 表示沒有子節點的節點"//*[count(*)=0 and not(text())]" 空節點,表示既沒有內容,也沒有子節點,但未排除包含屬性的節點
  • 一:Helios是什麼 Helios是一套高性能的Socket通信中間件,使用C#編寫。Helios的開發受到Netty的啟發,使用非阻塞的事件驅動模型架構來實現高併發高吞吐量。Helios為我們大大的簡化了Socket編程,它已經為我們處理好了高併發情況下的解包,粘包,buffer管理等等。 .....
  • Xamarin入門:包括了安裝相關,環境部署,以及一些常見的問題和一些資源。
  • 在 VS2013 下開發的 MVC4 網站,基於 .net 4.5,伺服器是一臺 Windows 2008 R2,運行的時候就報錯了The'targetFramework'attributeintheelementoftheWeb.configfileisusedonlytotargetversio...
  • 突然想到有關C#中使用event特性時關於線程安全的問題,以前雖然有遵從“複製引用+null判斷”的模式(盲目地),但沒有深入瞭解和思考。為之查詢了資料和實驗,對此有了進一步的理解。一般event使用模式定義(field-like event):public event EventHandler D...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...