首页 文章

PHP的收益意味着什么?

提问于
浏览
182

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

function xrange($min, $max) 
{
    for ($i = $min; $i <= $max; $i++) {
        yield $i;
    }
}

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

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

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

5 回答

  • 20

    什么是收益率?

    yield 关键字returns data from a generator function:

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

    什么是发电机功能?

    生成器函数实际上是一种更紧凑和有效的方式来编写Iterator . 它允许您定义一个函数(您的 xrange ),它将在您looping over it时计算并返回值:

    foreach (xrange(1, 10) as $key => $value) {
        echo "$key => $value", PHP_EOL;
    }
    

    这将创建以下输出:

    0 => 1
    1 => 2
    …
    9 => 10
    

    您还可以使用控制 foreach 中的 $key

    yield $someKey => $someValue;
    

    在生成器函数中, $someKey$key 出现的任何内容, $val$val 中的值 . 在问题's example that' s $i .

    与普通功能有什么区别?

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

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

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

    为什么需要呢?

    显然,生成器可用于解决内存限制 . 根据您的环境,执行 range(1, 1000000) 会使您的脚本致命,而使用生成器的情况也会正常工作 . 或者像维基百科所说:

    因为生成器仅根据需要计算其产生的值,所以它们对于表示一次性计算昂贵或无法计算的序列非常有用 . 这包括例如无限序列和实时数据流 .

    发电机也应该非常快 . 但请记住,当我们谈论快速时,我们通常会谈论的数量非常少 . 因此,在您现在运行并更改所有代码以使用生成器之前,请先执行基准测试以了解它的含义 .

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

    从什么时候开始使用收益?

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

    来源和进一步阅读:

  • 261

    simple example

    <?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#';
    ?>
    

    output

    #start main# {start[1,2,3,4,5,6,7,8,9,]end} #end main#
    
  • 13

    yield 关键字用于在PHP 5.5中定义"generators" . 好的,那么什么是generator

    来自php.net:

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

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

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

    • you need to do things simple (or simple things);

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

    • you need to generate BIG amounts of data - saving memory;

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

    • you need to generate sequence, which depends on intermediate values;

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

    • you need to improve performance.

    在某些情况下,它们可以更快地工作(参见上一个好处);

  • 16

    这个函数使用yield:

    function a($items) {
        foreach ($items as $item) {
            yield $item + 1;
        }
    }
    

    几乎没有这个没有:

    function b($items) {
        $result = [];
        foreach ($items as $item) {
            $result[] = $item + 1;
        }
        return $result;
    }
    

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

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

  • 6

    使用 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, ...);
    

    如果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);
    //...
    

    用生成器添加断点

    $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);
    

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

相关问题