PHP Conference Japan 2024

產生器概述

(PHP 5 >= 5.5.0, PHP 7, PHP 8)

產生器提供了一種簡單的方式來實現簡單的迭代器,而無需實現Iterator介面的類別所帶來的額外負擔或複雜性。

產生器提供了一種便利的方式,將資料提供給 foreach 迴圈,而不需事先在記憶體中建立陣列,因為這可能會導致程式超出記憶體限制,或需要相當多的處理時間來產生。相對地,可以使用產生器函式,它與一般的 函式 相同,不同之處在於,產生器可以 yield 任意次數,以提供要迭代的值,而不是只 return 一次。與迭代器一樣,無法進行隨機資料存取。

一個簡單的例子是將 range() 函式重新實作成產生器。標準的 range() 函式必須產生一個包含所有值的陣列並返回它,這可能會導致產生大型陣列:例如,呼叫 range(0, 1000000) 將會使用超過 100 MB 的記憶體。

作為替代方案,我們可以實作一個 xrange() 產生器,它只需要足夠的記憶體來建立一個 Iterator 物件並在內部追蹤產生器的目前狀態,而這只需要不到 1 KB 的記憶體。

範例 #1 將 range() 實作成產生器

<?php
function xrange($start, $limit, $step = 1) {
if (
$start <= $limit) {
if (
$step <= 0) {
throw new
LogicException('遞增值必須為正數');
}

for (
$i = $start; $i <= $limit; $i += $step) {
yield
$i;
}
} else {
if (
$step >= 0) {
throw new
LogicException('遞增值必須為負數');
}

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

/*
* 注意 range() 和 xrange() 都會產生以下相同的
* 輸出結果。
*/

echo '使用 range() 產生的個位數奇數: ';
foreach (
range(1, 9, 2) as $number) {
echo
"$number ";
}
echo
"\n";

echo
'使用 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

產生器 物件

當呼叫產生器函式時,會回傳一個新的內部 產生器 類別的物件。此物件實作了 迭代器 介面,其方式與僅向前迭代器物件非常相似,並提供可呼叫的方法來操作產生器的狀態,包括傳送值給產生器以及從產生器回傳值。

新增筆記

使用者貢獻的筆記 6 則筆記

bloodjazman at gmail dot com
11 年前
為了防止資源洩漏
參考 RFC https://wiki.php.net/rfc/generators#closing_a_generator

並使用 finally

範例程式碼

function getLines($file) {
$f = fopen($file, 'r');
try {
while ($line = fgets($f)) {
yield $line;
}
} finally {
fclose($f);
}
}

foreach (getLines("file.txt") as $n => $line) {
if ($n > 5) break;
echo $line;
}
montoriusz at gmail dot com
8 年前
請記住,生成器函式的執行會延遲到開始迭代其結果(Generator 物件)為止。如果將生成器的結果賦值給變數而不是立即迭代,這可能會造成混淆。

<?php

$some_state
= 'initial';

function
gen() {
global
$some_state;

echo
"gen() 執行開始\n";
$some_state = "changed";

yield
1;
yield
2;
}

function
peek_state() {
global
$some_state;
echo
"\$some_state = $some_state\n";
}

echo
"呼叫 gen()...\n";
$result = gen();
echo
"gen() 已被呼叫\n";

peek_state();

echo
"迭代中...\n";
foreach (
$result as $val) {
echo
"迭代: $val\n";
peek_state();
}

?>

如果您需要在函式被呼叫且在結果被使用之前執行某些動作,則必須將您的生成器包裝在另一個函式中。

<?php
/**
* @return Generator
*/
function some_generator() {
global
$some_state;

$some_state = "changed";
return
gen();
}
?>
chung1905 at gmail dot com
5 年前
補充 "montoriusz at gmail dot com" 的筆記:https://php.dev.org.tw/manual/en/language.generators.overview.php#119275

「如果您需要在函式被呼叫且在結果被使用之前執行某些動作,則必須將您的生成器包裝在另一個函式中。」
您可以改用 Generator::rewind (https://php.dev.org.tw/manual/en/generator.rewind.php)

範例程式碼
<?php
/** 函式/生成器定義 **/

echo "呼叫 gen()...\n";
$result = gen();
$result->rewind();
echo
"gen() 已被呼叫\n";

/** 迭代 **/
?>
info at boukeversteegh dot nl
8 年前
以下是偵測迴圈中斷,以及如何在中斷後處理或清理的方法。

<?php
function generator()
{
$complete = false;
try {

while ((
$result = some_function())) {
yield
$result;
}
$complete = true;

} finally {
if (!
$complete) {
// 迴圈中斷時的清理工作
} else {
// 迴圈完成時的清理工作
}
}

// 只在迴圈完成後執行
}
?>
lubaev
10 年前
抽象測試。
<?php

$start_time
=microtime(true);
$array = array();
$result = '';
for(
$count=1000000; $count--;)
{
$array[]=$count/2;
}
foreach(
$array as $val)
{
$val += 145.56;
$result .= $val;
}
$end_time=microtime(true);

echo
"時間: ", bcsub($end_time, $start_time, 4), "\n";
echo
"記憶體 (位元組): ", memory_get_peak_usage(true), "\n";

?>

<?php

$start_time
=microtime(true);
$result = '';
function
it()
{
for(
$count=1000000; $count--;)
{
yield
$count/2;
}
}
foreach(
it() as $val)
{
$val += 145.56;
$result .= $val;
}
$end_time=microtime(true);

echo
"time: ", bcsub($end_time, $start_time, 4), "\n";
echo
"memory (byte): ", memory_get_peak_usage(true), "\n";

?>
結果
----------------------------------
| 時間 | 記憶體 (MB) |
----------------------------------
| 非產生器 | 2.1216 | 89.25 |
|---------------------------------
| 使用產生器 | 6.1963 | 8.75 |
|---------------------------------
| 差異 | < 192% | > 90% |
----------------------------------
dc at libertyskull dot com
10 年前
相同的範例,不同的結果

----------------------------------
| 時間 | 記憶體 (MB) |
----------------------------------
| 非產生器 | 0.7589 | 146.75 |
|---------------------------------
| 使用產生器 | 0.7469 | 8.75 |
|---------------------------------

兩個範例的結果時間都介於 6.5 到 7.8 之間。
因此,在處理速度方面沒有明顯的缺點。
To Top