PHP的收益意味着什么?

Gor*_*don 214 php generator yield-keyword php-5.5

我最近偶然发现了这段代码:

function xrange($min, $max) 
{
    for ($i = $min; $i <= $max; $i++) {
        yield $i;
    }
}
Run Code Online (Sandbox Code Playgroud)

我以前从未见过这个yield关键字.试着运行我得到的代码

解析错误:语法错误,第x行意外的T_VARIABLE

那么这个yield关键字是什么?它甚至是有效的PHP吗?如果是,我该如何使用它?

Gor*_*don 321

什么是yield

yield关键字从发电机函数返回数据:

生成器函数的核心是yield关键字.在最简单的形式中,yield语句看起来很像一个return语句,除了不是停止执行函数并返回,而是yield为循环生成器的代码提供一个值并暂停生成器函数的执行.

什么是发电机功能?

生成器函数实际上是编写迭代器的更紧凑和有效的方式.它允许您定义一个函数(您的xrange),它将循环计算和返回值:

foreach (xrange(1, 10) as $key => $value) {
    echo "$key => $value", PHP_EOL;
}
Run Code Online (Sandbox Code Playgroud)

这将创建以下输出:

0 => 1
1 => 2
…
9 => 10
Run Code Online (Sandbox Code Playgroud)

您也可以控制$keyforeach使用

yield $someKey => $someValue;
Run Code Online (Sandbox Code Playgroud)

在生成器函数中,$someKey是您想要出现的任何内容$key并且$someValue是其中的值$val.在问题的例子中$i.

普通功能有什么区别?

现在您可能想知道为什么我们不是简单地使用PHP的本机range函数来实现该输出.对,你是对的.输出将是相同的.不同之处在于我们如何到达那里.

当我们使用rangePHP,将执行它,在内存中并创建数字的整个阵列return整个阵列foreach循环,然后将去在它和输出的值.换句话说,foreach将在阵列本身上运行.该range功能和foreach唯一的"谈话"一次.把它想象成在邮件中获取包裹.送货员将递给您包裹并离开.然后你解开整个包裹,取出那里的东西.

当我们使用生成器函数时,PHP将进入函数并执行它,直到它满足结束或yield关键字.当它遇到a时yield,它将返回当时的值到外循环.然后它返回到生成器功能并从它产生的地方继续.由于你xrange持有一个for循环,它将执行并产生直到$max达到.把它想象成foreach和打乒乓球的发电机一样.

我为什么需要那个?

显然,生成器可用于解决内存限制.根据你的环境,做一个range(1, 1000000)致命的你的脚本,而与生成器相同将工作正常.或者像维基百科所说:

由于生成器仅根据需要计算其生成的值,因此它们可用于表示一次性计算昂贵或无法计算的序列.这些包括例如无限序列和实时数据流.

发电机也应该非常快.但请记住,当我们谈论快速时,我们通常会谈论很少的数字.因此,在您现在运行并更改所有代码以使用生成器之前,请执行基准测试以查看其有意义的位置.

生成器的另一个用例是异步协同程序.该yield关键字不仅返回值,但它也接受他们.有关详细信息,请参阅下面链接的两篇优秀博客文章.

从什么时候开始使用yield

PHP 5.5中引入了生成器.尝试yield在该版本之前使用将导致各种解析错误,具体取决于关键字后面的代码.因此,如果您从该代码中获得解析错误,请更新您的PHP.

来源和进一步阅读:


tsu*_*nka 30

这个函数使用yield:

function a($items) {
    foreach ($items as $item) {
        yield $item + 1;
    }
}
Run Code Online (Sandbox Code Playgroud)

几乎没有这个没有:

function b($items) {
    $result = [];
    foreach ($items as $item) {
        $result[] = $item + 1;
    }
    return $result;
}
Run Code Online (Sandbox Code Playgroud)

只有一个区别是a()返回一个生成器b()只是一个简单的数组.你可以迭代两者.

此外,第一个不分配完整数组,因此对内存要求较低.

  • 官方文档的补充说明:在PHP 5中,生成器无法返回值:这样做将导致编译错误。空的return语句是生成器中的有效语法,它将终止生成器。从PHP 7.0开始,生成器可以返回值,可以使用Generator :: getReturn()进行检索。http://php.net/manual/zh/language.generators.syntax.php (2认同)
  • 简单、简洁。 (2认同)

QAr*_*rea 21

yieldkeyword用于定义PHP 5.5中的"generators".好的,那么什么是发电机

来自php.net:

生成器提供了一种简单的方法来实现简单的迭代器,而无需实现实现Iterator接口的类的开销或复杂性.

生成器允许您编写使用foreach迭代一组数据的代码,而无需在内存中构建数组,这可能会导致超出内存限制,或者需要相当长的处理时间来生成.相反,您可以编写一个生成器函数,它与普通函数相同,除了不是返回一次,生成器可以生成所需的次数,以便提供要迭代的值.

从这个地方:generators = generator,其他函数(只是一个简单的函数)=函数.

因此,它们在以下情况下很有用

  • 你需要做简单的事情(或简单的事情);

    生成器比实现Iterator接口要简单得多.另一方面,发电机的功能较少.比较它们.

  • 你需要生成大量的数据 - 节省内存;

    实际上为了节省内存,我们可以通过每次循环迭代的函数生成所需的数据,并在迭代后使用垃圾.所以这里的要点是 - 清晰的代码和可能的性能.看看哪个更适合您的需求.

  • 你需要生成序列,这取决于中间值;

    这是以前思想的延伸.与函数相比,生成器可以使事情变得更容易.检查Fibonacci示例,并尝试在没有生成器的情况下生成序列.在这种情况下,生成器也可以更快地工作,至少是因为将中间值存储在局部变量中;

  • 你需要提高性能.

    在某些情况下,它们可以更快地运行(见前面的好处);


Thi*_*Big 20

简单的例子

<?php
echo '#start main# ';
function a(){
    echo '{start[';
    for($i=1; $i<=9; $i++)
        yield $i;
    echo ']end} ';
}
foreach(a() as $v)
    echo $v.',';
echo '#end main#';
?>
Run Code Online (Sandbox Code Playgroud)

产量

#start main# {start[1,2,3,4,5,6,7,8,9,]end} #end main#
Run Code Online (Sandbox Code Playgroud)

  • 那么,它返回时不会中断函数吗? (3认同)

CSS*_*ner 19

上面的答案都没有显示使用由非数字成员填充的大量数组的具体示例。这是一个使用由explode()大型 .txt 文件(在我的用例中为 262MB)上生成的数组的示例:

<?php

ini_set('memory_limit','1000M');

echo "Starting memory usage: " . memory_get_usage() . "<br>";

$path = './file.txt';
$content = file_get_contents($path);

foreach(explode("\n", $content) as $ex) {
    $ex = trim($ex);
}

echo "Final memory usage: " . memory_get_usage();
Run Code Online (Sandbox Code Playgroud)

输出是:

Starting memory usage: 415160
Final memory usage: 270948256
Run Code Online (Sandbox Code Playgroud)

现在使用yield关键字将其与类似的脚本进行比较:

<?php

ini_set('memory_limit','1000M');

echo "Starting memory usage: " . memory_get_usage() . "<br>";

function x() {
    $path = './file.txt';
    $content = file_get_contents($path);
    foreach(explode("\n", $content) as $x) {
        yield $x;
    }
}

foreach(x() as $ex) {
    $ex = trim($ex);
}

echo "Final memory usage: " . memory_get_usage();
Run Code Online (Sandbox Code Playgroud)

这个脚本的输出是:

Starting memory usage: 415152
Final memory usage: 415616
Run Code Online (Sandbox Code Playgroud)

显然,内存使用节省是相当可观的(?MemoryUsage ----->第一个示例中的~270.5 MB,第二个示例中的~450B)。


inf*_*rno 7

有了yield你可以很容易地描述一个函数的多个任务之间的断点.这就是全部,没有什么特别之处.

$closure = function ($injected1, $injected2, ...){
    $returned = array();
    //task1 on $injected1
    $returned[] = $returned1;
//I need a breakpoint here!!!!!!!!!!!!!!!!!!!!!!!!!
    //task2 on $injected2
    $returned[] = $returned2;
    //...
    return $returned;
};
$returned = $closure($injected1, $injected2, ...);
Run Code Online (Sandbox Code Playgroud)

如果task1和task2高度相关,但您需要在它们之间使用断点来执行其他操作:

  • 处理数据库行之间的空闲内存
  • 运行其他任务,提供对下一个任务的依赖,但通过理解当前代码无关
  • 进行异步调用并等待结果
  • 等等 ...

那么发电机是最好的解决办法,因为你没有给你的代码分成许多倒闭或与其他代码混合在一起,或使用回调,等等......你只需要使用yield添加断点,并且您可以从继续断点如果你准备好了.

添加没有生成器的断点:

$closure1 = function ($injected1){
    //task1 on $injected1
    return $returned1;
};
$closure2 = function ($injected2){
    //task2 on $injected2
    return $returned1;
};
//...
$returned1 = $closure1($injected1);
//breakpoint between task1 and task2
$returned2 = $closure2($injected2);
//...
Run Code Online (Sandbox Code Playgroud)

用生成器添加断点

$closure = function (){
    $injected1 = yield;
    //task1 on $injected1
    $injected2 = (yield($returned1));
    //task2 on $injected2
    $injected3 = (yield($returned2));
    //...
    yield($returnedN);
};
$generator = $closure();
$returned1 = $generator->send($injected1);
//breakpoint between task1 and task2
$returned2 = $generator->send($injected2);
//...
$returnedN = $generator->send($injectedN);
Run Code Online (Sandbox Code Playgroud)

注意:生成器很容易出错,所以在实现它们之前一定要编写单元测试! 注意2:在无限循环中使用生成器就像编写一个具有无限长度的闭包......