深入了解PHP中生成器yield的使用

目录
  • 1. 什么是 "yield"
  • 2.yield 解决的问题
  • 3."yield" & "return" 的区别
  • 4. 什么是 "yield" 选项
  • 5. 生成器

如果是做Python或者其他语言的小伙伴,对于生成器应该不陌生。但很多PHP开发者或许都不知道生成器这个功能,可能是因为生成器是PHP 5.5.0才引入的功能,也可以是生成器作用不是很明显。但是,生成器功能的确非常有用。

1. 什么是 "yield"

生成器函数看上去就像一个普通函数, 除了不是返回一个值之外, 生成器会根据需求产生更多的值。

看以下的例子:

function getValues() {
    yield 'value';
}

// 输出字符串 "value"
echo getValues();

当然, 这不是他生效的方式, 前面的例子会给你一个致命的错误: 类生成器的对象不能被转换成字符串

2.yield 解决的问题

解决运行内存的瓶颈,php程序中的变量存储在内存中,之前有遇到过读取Excel文件时候,会出现内存不足,出现:

Fatal Error: Allowed memory size of xxxxxx bytes

所以会设置php 最大运行内存的设置: ini_set('memory_limit', '200M')

但是当我们读取5g 这么大的文件的时候,我们运行内存可能就吃不消了,所以会选择yield

3."yield" & "return" 的区别

前面的错误意味着 getValues() 方法不会如预期返回一个字符串,让我们检查一下他的类型:

function getValues() {
    return 'value';
}
var_dump(getValues()); // string(5) "value"

function getValues() {
    yield 'value';
}
var_dump(getValues()); // class Generator#1 (0) {}

生成器 类实现了 生成器 接口, 这意味着你必须遍历 getValue() 方法来取值:

foreach (getValues() as $value) {
   echo $value;
}

// 使用变量也是好的
$values = getValues();
foreach ($values as $value) {
   echo $value;
}

但这不是唯一的不同!

一个生成器运行你写使用循环来迭代一维数组的代码,而不需要在内存中创建是一个数组,这可能会导致你超出内存限制。

在下面的例子里我们创建一个有 800,000 元素的数字同时从 getValues() 方法中返回他,同时在此期间,我们将使用函数 memory_get_usage() 来获取分配给次脚本的内存, 我们将会每增加 200,000 个元素来获取一下内存使用量,这意味着我们将会提出四个检查点:

<?php
function getValues() {
   $valuesArray = [];
   // 获取初始内存使用量
   echo round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL;
   for ($i = 1; $i < 800000; $i++) {
      $valuesArray[] = $i;
      // 为了让我们能进行分析,所以我们测量一下内存使用量
      if (($i % 200000) == 0) {
         // 来 MB 为单位获取内存使用量
         echo round(memory_get_usage() / 1024 / 1024, 2) . ' MB'. PHP_EOL;
      }
   }
   return $valuesArray;
}
$myValues = getValues(); // 一旦我们调用函数将会在这里创建数组
foreach ($myValues as $value) {}

前面例子发生的情况是这个脚本的内存消耗和输出:

0.34 MB
8.35 MB
16.35 MB
32.35 MB

这意味着我们的几行脚本消耗了超过 30 MB 的内存, 每次你你添加一个元素到 $valuesArray 数组中, 都会增加他在内存中的大小。

让我们使用 yield 同样的例子:

<?php
function getValues() {
   // 获取内存使用数据
   echo round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL;
   for ($i = 1; $i < 800000; $i++) {
      yield $i;
      // 做性能分析,因此可测量内存使用率
      if (($i % 200000) == 0) {
         // 内存使用以 MB 为单位
         echo round(memory_get_usage() / 1024 / 1024, 2) . ' MB'. PHP_EOL;
      }
   }
}
$myValues = getValues(); // 在循环之前都不会有动作
foreach ($myValues as $value) {} // 开始生成数据

这个脚本的输出令人惊讶:

0.34 MB
0.34 MB
0.34 MB
0.34 MB

这不意味着你从 return 表达式迁移到 yield,但如果你在应用中创建会导致服务器上内存出问题的巨大数组,则 yield 更加适合你的情况。

4. 什么是 "yield" 选项

这里有很多 yield 的选项, 我将强调他们中的几个:

a. 使用 yield, 你也可以使用 return。

function getValues() {
   yield 'value';
   return 'returnValue';
}
$values = getValues();
foreach ($values as $value) {}
echo $values->getReturn(); // 'returnValue'

b. 返回键值对:

function getValues() {
   yield 'key' => 'value';
}
$values = getValues();
foreach ($values as $key => $value) {
   echo $key . ' => ' . $value;
}

5. 生成器

1 使用生成器来生成一个1到100的数组

function  my_range($start,$limit){
    for($i=$start;$i<=$limit;$i++){
        yield $i;
    }
}

2 打印出来,看下返回究竟是什么

$arr = my_range(1,100);
var_dump($arr);

结果是:

object(Generator)#1 (0) {
}

可见是一个对象,是一个生成器对象,既然是对象那么也就是可以用foreach来遍历

3 遍历生成器

foreach($arr as $num){
    echo $num.PHP_EOL;
}

看到可以完整遍历出来,那么与那样实现的不同地方,意义在哪里呢。重点来了。

4 两者内存占用比较

上面已经测试过使用数组的方式,随着范围的增大占用的内存剧增,很快就超过了PHP的内存上限。

那么使用生成器占用了多少内存呢,来看看就知道了。

$start = memory_get_usage();
$arr   = my_range(1, 100);
$end   = memory_get_usage();
echo $end - $start .' bytes'.PHP_EOL;

可以看到只占用了576bytes,当然每个人测试的可能都会有点不同,环境不同,但是这不是重点。

我们再尝试增加数字范围,可以看到数字范围并没有影响到内存占用,也就是可以轻松的遍历超大数字。

$start = memory_get_usage();
$arr   = my_range(1, 100000000);
$end   = memory_get_usage();
echo $end - $start .' bytes'.PHP_EOL;

foreach($arr as $num){
    echo $num.PHP_EOL;
}

这下我们就可以遍历1到10000000的数字了,不相信内存占用那么低的小伙伴,可以打开任务管理器毫无波澜,即时再上调数字范围。

5、生成器遍历原理

生成器既然这么强大,那么他的遍历原理是什么呢。使用foreach遍历的时候,相当于生成器执行了以下操作。

while($arr->valid()){
    echo $arr->current().PHP_EOL;
    $arr->next();
}
//$arr->valid()     判断生成器是否关闭
//$arr->current() 返回当前对象
//$arr->next()    继续往下执行生成器

到此这篇关于深入了解PHP中生成器yield的使用的文章就介绍到这了,更多相关PHP生成器yield内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 深入理解PHP中的static和yield关键字

    前言 本文主要给大家介绍了关于PHP中static和yield关键字的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 先来说说 static 关键字.本篇只讲静态方法的使用与后期绑定的知识点. static 什么时候用来修饰方法 static 关键字大家都知道是用来修饰方法与属性的. 那么大家在项目中会在哪些场景下使用它? 我遇到过几个项目,要求所有的方法全部 static 化,当然控制器方法不能这么干.原因之一就是:静态方法执行效率高?那么我们基于此来分析一下. 首

  • PHP yield关键字功能与用法分析

    本文实例讲述了PHP yield关键字功能与用法.分享给大家供大家参考,具体如下: yield 关键字是php5.5版本推出的一个特性.生成器函数的核心是yield关键字.它最简单的调用形式看起来像一个return申明,不同之处在于普通return会返回值并终止函数的执行,而yield会返回一个值给循环调用此生成器的代码并且只是暂停执行生成器函数. Example #1 一个简单的生成值的例子 <?php function gen_one_to_three() { for ($i = 1; $i

  • php使用yield对性能提升的测试实例分析

    本文实例讲述了php使用yield对性能提升的测试.分享给大家供大家参考,具体如下: 生成器提供了一种更容易的方法来实现简单的对象迭代,相比较定义类实现 Iterator 接口的方式,性能开销和复杂性大大降低.生成器允许你在 foreach 代码块中写代码来迭代一组数据而不需要在内存中创建一个数组, 那会使你的内存达到上限,或者会占据可观的处理时间.相反,你可以写一个生成器函数,就像一个普通的自定义函数一样, 和普通函数只返回一次不同的是, 生成器可以根据需要 yield 多次,以便生成需要迭代

  • PHP 生成器的使用详解

    什么是生成器? 听着高大上的名字,感觉像是创造什么东西的一个功能,实际上,生成器是一个用于迭代的迭代器.它提供了一种更容易的方式来实现简单的对象迭代,相比较定义类实现Iterator接口的方式,性能开销和复杂性大大降低. 说了半天不如直接看看代码更直观. function test1() { for ($i = 0; $i < 3; $i++) { yield $i + 1; } yield 1000; yield 1001; } foreach (test1() as $t) { echo $

  • PHP生成器功能与用法实例分析

    本文实例讲述了PHP生成器功能与用法.分享给大家供大家参考,具体如下: 1. 官方说明:生成器提供了一种更容易的方法来实现简单的对象迭代,相比较定义类实现 Iterator 接口的方式,性能开销和复杂性大大降低.生成器允许你在 foreach 代码块中写代码来迭代一组数据而不需要在内存中创建一个数组. 2. 生成器就像一个普通的自定义函数一样, 和普通函数只返回一次不同的是, 生成器可以根据需要 yield 多次,以便生成需要迭代的值. 3. 代码示例: //未使用生成器 echo '开始内存:

  • PHP生成器简单实例

    一般你在迭代一组数据的时候,需要创建一个数据,假设数组很大,则会消耗很大性能,甚至造成内存不足. 复制代码 代码如下: //Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 32 bytes) in E:\php\test\index.php on line 5 range(1, 100000000); PHP5.5实现了生成器,每当产生一个数组元素则用yield关键词返回,并且执行函

  • 初步解析Python中的yield函数的用法

    您可能听说过,带有 yield 的函数在 Python 中被称之为 generator(生成器),何谓 generator ? 我们先抛开 generator,以一个常见的编程题目来展示 yield 的概念. 如何生成斐波那契數列 斐波那契(Fibonacci)數列是一个非常简单的递归数列,除第一个和第二个数外,任意一个数都可由前两个数相加得到.用计算机程序输出斐波那契數列的前 N 个数是一个非常简单的问题,许多初学者都可以轻易写出如下函数: 清单 1. 简单输出斐波那契數列前 N 个数 def

  • python3.6生成器yield用法实例分析

    本文实例讲述了python3.6生成器yield用法.分享给大家供大家参考,具体如下: 今天看源码的时候看到了一个比较有意思的函数:yield 功能与return类似,都是返回定义的函数的一个结果,不同的是return返回后这次调用函数就结束了,除了返回值,其余临时变量都会被清除.而yield会停止在当前步,并保留其余变量的值,等下次调用该函数时,从yield的下一步继续往下运行. yield的好处是如果函数需要很大的内存,比方说需要计算并返回一个很大的数列,如果用return,我们只能用一个l

  • 深入浅析Python中的yield关键字

    前言 python中有一个非常有用的语法叫做生成器,所利用到的关键字就是yield.有效利用生成器这个工具可以有效地节约系统资源,避免不必要的内存占用. 一段代码 def fun(): for i in range(20): x=yield i print('good',x) if __name__ == '__main__': a=fun() a.__next__() x=a.send(5) print(x) 这段代码很短,但是诠释了yield关键字的核心用法,即逐个生成.在这里获取了两个生成

  • Python中生成器和迭代器的区别详解

    Python中生成器和迭代器的区别(代码在Python3.5下测试): Num01–>迭代器 定义: 对于list.string.tuple.dict等这些容器对象,使用for循环遍历是很方便的.在后台for语句对容器对象调用iter()函数.iter()是python内置函数. iter()函数会返回一个定义了next()方法的迭代器对象,它在容器中逐个访问容器内的元素.next()也是python内置函数.在没有后续元素时,next()会抛出一个StopIteration异常,通知for语句

  • 理解python中生成器用法

    生成器(generator)概念 生成器不会把结果保存在一个系列中,而是保存生成器的状态,在每次进行迭代时返回一个值,直到遇到StopIteration异常结束. 生成器语法 生成器表达式: 通列表解析语法,只不过把列表解析的[]换成() 生成器表达式能做的事情列表解析基本都能处理,只不过在需要处理的序列比较大时,列表解析比较费内存. >>> gen = (x**2 for x in range(5)) >>> gen <generator object <

  • 基于Python中的yield表达式介绍

    python生成器 python中生成器是迭代器的一种,使用yield返回函数值.每次调用yield会暂停,而可以使用next()函数和send()函数可以恢复生成器. 这里可以参考Python函数式编程指南:对生成器全面讲解 注意到yield是个表达式而不仅仅是个语句,所以可以使用x = yield r 这样的语法. 这个知识点在协程中需要使用.协程的概念指的是在一个线程内,一个程序中断去执行另一个程序,有点类似于CPU中断.这样减少了切换线程带来的负担,同时不需要多线程中的锁机制,因为不存在

  • Python 中由 yield 实现异步操作

    yield在python中初学时,觉得比较难理解.yield的作用: ①返回一个值.②接收调用者的参数 分析下面的代码: #!/usr/bin/env python3 # -*- coding:utf-8 -*- def consumer(): r = '' while True: n = yield r print("[Consumer] n = %d" %n) if not n: return print("[Consumer] consuming %s..."

  • 由浅入深讲解python中的yield与generator

    前言 本文将由浅入深详细介绍yield以及generator,包括以下内容:什么generator,生成generator的方法,generator的特点,generator基础及高级应用场景,generator使用中的注意事项.本文不包括enhanced generator即pep342相关内容,这部分内容在之后介绍. generator基础 在python的函数(function)定义中,只要出现了yield表达式(Yield expression),那么事实上定义的是一个generator

  • python生成器/yield协程/gevent写简单的图片下载器功能示例

    本文实例讲述了python生成器/yield协程/gevent写简单的图片下载器功能.分享给大家供大家参考,具体如下: 1.生成器: '''第二种生成器''' # 函数只有有yield存在就是生成器 def test(i): while True: i += 1 res = yield i print(res) i += 1 return res def main(): t = test(1) # 创建生成器对象 print(next(t)) # next第一次执行从上到下,yield是终点 p

  • Python中生成器和yield语句的用法详解

    在开始课程之前,我要求学生们填写一份调查表,这个调查表反映了它们对Python中一些概念的理解情况.一些话题("if/else控制流" 或者 "定义和使用函数")对于大多数学生是没有问题的.但是有一些话题,大多数学生只有很少,或者完全没有任何接触,尤其是"生成器和yield关键字".我猜这对大多数新手Python程序员也是如此. 有事实表明,在我花了大功夫后,有些人仍然不能理解生成器和yield关键字.我想让这个问题有所改善.在这篇文章中,我将解

随机推荐

其他