深入理解python的生成器表达式和列表解析

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 没有用过的东西,没有深刻理解的东西很难说自己会,而且被别人一问必然破绽百出。虽然之前有接触过python协程的概念,但是只是走马观花,这两天的一次交谈中,别人问到了协程,顿时语塞,死活想不起来曾经看过的东西,之后突然想到了yield,但为时已晚,只能说概念不清,所以本篇先缕缕python的生成器和yield关键字。

前言

     没有用过的东西,没有深刻理解的东西很难说自己会,而且被别人一问必然破绽百出。虽然之前有接触过python协程的概念,但是只是走马观花,这两天的一次交谈中,别人问到了协程,顿时语塞,死活想不起来曾经看过的东西,之后突然想到了yield,但为时已晚,只能说概念不清,所以本篇先缕缕python的生成器和yield关键字


什么是生成器

  • 生成器是一个特殊的程序,可以被用作控制循环的迭代行为
  • 生成器类似于返回值为数组的一个函数,这个函数可以接收参数,可以被调用,但是,不同于一般的函数会一次性返回包含了所有数值的数组,生成器一次只产生一个值,这样消耗的内存数量大大减少,而且允许调用函数可以很快的开始处理前几个返回值。因此,生成器看起来像一个函数但是表现的却像一个迭代器。


python中的生成器

python提供了两种基本的方式。

  • 生成器函数:也是用def来定义,利用关键字yield一次返回一个结果,阻塞,重新开始
  • 生成器表达式:返回一个对象,这个对象只有在需要的时候才产生结果


下面详细讲解:


生成器函数

  • 为什么叫生成器函数?因为他随着时间的推移生成了一个数值队列。一般的函数在执行完毕之后会返回一个值然后退出,但是生成器函数会自动挂起,然后重新拾起继续执行,他会利用yield关键字关起函数,给调用者返回一个值,同时保留了当前的足够多的状态,可以使函数继续执行。生成器和迭代协议是密切相关的,可迭代的对象都有一个__next()__成员方法,这个方法要么返回迭代的下一项,要么引起异常结束迭代
  • 为了支持迭代协议,拥有yield语句的函数被编译为生成器,这类函数被调用时返回一个生成器对象,返回的对象支持迭代接口,即成员方法__next()__继续从中断处执行执行。


看下面的例子:

  1. # 计数器
  2. defcreate_counter(n):
  3.    print("create counter")
  4.    whileTrue:
  5.        yield n
  6.        print('increment n')
  7.        n += 1
  8. cnt = create_counter(2)
  9. print(cnt)
  10. print(next(cnt))
  11. print(next(cnt))
  12. print(next(cnt))
  13. print(next(cnt))
  14. # D:\Python\python\python-3.6.1\Python36-64\python.exe
  15. # <generator object create_counter at 0x0000000002271D00>
  16. # create counter
  17. # 2
  18. # increment n
  19. # 3
  20. # increment n
  21. # 4
  22. # increment n
  23. # 5



分析一下这个例子:

  • 在create_counter函数中出现了关键字yield,预示着这个函数每次只产生一个结果值,这个函数返回一个生成器(通过第一行输出可以看出来),用来产生连续的n值
  • 在创造生成器实例的时候,只需要像普通函数一样调用就可以,但是这个调用却不会执行这个函数,这个可以通过输出看出来
  • next()函数将生成器对象作为自己的参数,在第一次调用的时候,他执行了create_counter()函数到yield语句,返回产生的值2
  • 我们重复的调用next()函数,每次他都会从上次被挂起的地方开始执行,直到再次遇到了yield关键字

为了更加深刻的理解,我们再举一个例子。

  1. # 立方
  2. defcube(n):
  3.    for i inrange(n):
  4.        yield i ** 3
  5. print(cube(5))
  6. print(next(cube(5)))
  7. print(next(cube(5)))
  8. print(next(cube(5)))
  9. print("for循环")
  10. for i in cube(5):
  11.    print(i)
  12. # D:\Python\python\python-3.6.1\Python36-64\python.exe
  13. # <generator object cube at 0x0000000001FA1D00>
  14. # 0
  15. # 0
  16. # 0
  17. # for循环
  18. # 0
  19. # 1
  20. # 8
  21. # 27
  22. # 64


所以从理解函数的角度出发我们可以将yield类比为return,但是功能确实完全不同,在for循环中,会自动遵循迭代规则,每次调用next()函数,所以上面的结果不难理解。

生成器表达式


生成器表达式来自于迭代和列表解析的组合,生成器表达式和列表解析类似,但是他使用圆括号而不是方括号括起来的。如下代码:

  1. # 列表解析生成列表
  2. list = [ x ** 3for x inrange(5)]
  3. print(list)
  4. # [0, 1, 8, 27, 64]
  5. # 生成器表达式
  6. range_ = (x ** 3for x inrange(5))
  7. print(range_)
  8. print(range_.__next__())
  9. print(range_.__next__())
  10. print(range_.__next__())
  11. # <generator object <genexpr> at 0x00000000024A1D00>
  12. # 0
  13. # 1
  14. # 8
  15. # 两者之间转换
  16. list1 = list(x ** 3for x inrange(5))
  17. print(list1)
  18. # Traceback (most recent call last):
  19. #   File "test.py", line 29, in <module>
  20. #     list1 = list(x ** 3 for x in range(5))
  21. # TypeError: 'list' object is not callable


就操作而言,生成器表如果使用大量的next()函数会显得十分不方便,for循环会自动出发next函数,所以可以按下面方式使用:

  1. for n in (x ** 3for x inrange(5)):
  2.    print('%s, %s' % (n, n * n))
  3. # 0, 0
  4. # 1, 1
  5. # 8, 64
  6. # 27, 729
  7. # 64, 4096


关于列表解析的概念和用法如下:


列表是一个很有用的数据结构,由于其灵活性在实际应用中被广泛使用。对于列表来说,列表解析十分常用。

列表解析的语法如下,它迭代iterable中的每一个元素,当条件满足的时候便根据表达式expr计算的内容生成一个元素并放入新的列表中,依次类推,最终返回整个列表。

  1. [expr for iter_item in iterable if cond_expr]


列表解析的使用非常灵活:

  • 支持多重嵌套,如果需要生成一个二维列表可以使用列表解析嵌套的方式

  1. nested_list = [['Hello', 'World'],['Goodbye', 'World']]
  2. print(nested_list)
  3. # [['Hello', 'World'], ['Goodbye', 'World']]
  4. nested_list =  [[ele.upper() for ele in word] for word in nested_list]
  5. print(nested_list)
  6. # [['HELLO', 'WORLD'], ['GOODBYE', 'WORLD']]


  • 支持多重迭代

  1. b_ = [(a, b) for a in ['1', '2', '3', '4'] for b in ['a', 'b', 'c', 'd'] if a != b]
  2. print(b_)
  3. # [('1', 'a'), ('1', 'b'), ('1', 'c'), ('1', 'd'),
  4. # ('2', 'a'), ('2', 'b'), ('2', 'c'), ('2', 'd'),
  5. # ('3', 'a'), ('3', 'b'), ('3', 'c'), ('3', 'd'),
  6. # ('4', 'a'), ('4', 'b'), ('4', 'c'), ('4', 'd')]


  • 表达式可以是简单表达式,也可以是复杂表达式,甚至是函数

  1. deff(v):
  2.    if v % 2 == 0:
  3.        v = v ** 2
  4.    else:
  5.        v = v + 1
  6.    return v
  7. print([f(v) for v in [1, 2, 3, -1] if v > 0])
  8. print([v ** 2if v % 2 == 0else v + 1for v in [1, 2, 3, -1] if v > 0])
  9. # [2, 4, 4]
  10. # [2, 4, 4]


  • iterable可以是任意可迭代对象

  1. fp = open('wdf.py','r')
  2. res = [i for i in fp if'weixin'in i]
  3. print(res)


为什么要推荐在需要生成列表的时候使用列表解析呢?

  • 使用列表解析更为直观清晰,代码更为简洁
  • 列表解析的效率更高,但是对于大数据处理,列表解析并不是一个最佳选择,过多的内存消耗可能会导致MemoryError

除了列表可以使用列表解析的语法之外,其他内置的数据结构也支持,如下:

  1. #generator
  2. (expr for iter_item in iterable if cond_expr)
  3. #set
  4. {expr for iter_item in iterable if cond_expr}
  5. #dict
  6. {expr1: expr2 for iter_item in iterable if cond_expr}





两者比较

       一个迭代既可以被写成生成器函数,也可以被写成生成器表达式,均支持自动和手动迭代。而且这些生成器只支持一个active迭代,也就是说生成器的迭代器就是生成器本身。


       绝大多数情况下,遍历一个集合都是为了对元素应用某个动作或是进行筛选。你应该还记得有内建函数map和filter提供了这些功能,但Python仍然为这些操作提供了语言级的支持。

  1. (x+1for x in lst) #生成器表达式,返回迭代器。外部的括号可在用于参数时省略。
  2. [x+1for x in lst] #列表解析,返回list


    如你所见,生成器表达式和列表解析的区别很小,所以人们提到这个特性时,简单起见往往只描述成列表解析。然而由于返回迭代器时,并不是在一开始就计算所有的元素,这样能得到更多的灵活性并且可以避开很多不必要的计算,所以除非你明确希望返回列表,否则应该始终使用生成器表达式

你也可以为列表解析提供if子句进行筛选:

  1. (x+1for x in lst if x!=0)


  或者提供多条for子句进行嵌套循环,嵌套次序就是for子句的顺序:

  1. ((x, y) for x inrange(3) for y inrange(x))


     列表解析就是鲜明的Pythonic。我常遇到两个使用列表解析的问题:

  1. 第一个问题是,因为对元素应用的动作太复杂,不能用一个表达式写出来,所以不使用列表解析。这是典型的思想没有转变的例子,如果我们将动作封装成函数,那不就是一个表达式了么?
  2. 第二个问题是,因为if子句里的条件需要计算,同时结果也需要进行同样的计算,不希望计算两遍,就像这样:
  3. (x.doSomething() for x in lst if x.doSomething()>0)


这样写确实很糟糕,但组合一下列表解析即可解决:

  1. (x for x in (y.doSomething() for y in lst) if x>0)


内部的列表解析变量其实也可以用x,但为清晰起见我们改成了y。或者更清楚的,可以写成两个表达式:

  1. tmp = (x.doSomething() for x in lst)
  2. (x for x in tmp if x > 0)


列表解析可以替代绝大多数需要用到map和filter的场合,可能正因为此,著名的静态检查工具pylint将map和filter的使用列为了警告。



参考来源: http://www.cnblogs.com/cotyb/p/5260032.html

参考来源: http://www.cnblogs.com/cotyb/p/5111064.html

参考来源http://www.jb51.net/article/80741.htm


目录
相关文章
|
18天前
|
存储 算法 安全
控制局域网上网软件之 Python 字典树算法解析
控制局域网上网软件在现代网络管理中至关重要,用于控制设备的上网行为和访问权限。本文聚焦于字典树(Trie Tree)算法的应用,详细阐述其原理、优势及实现。通过字典树,软件能高效进行关键词匹配和过滤,提升系统性能。文中还提供了Python代码示例,展示了字典树在网址过滤和关键词屏蔽中的具体应用,为局域网的安全和管理提供有力支持。
48 17
|
21天前
|
运维 Shell 数据库
Python执行Shell命令并获取结果:深入解析与实战
通过以上内容,开发者可以在实际项目中灵活应用Python执行Shell命令,实现各种自动化任务,提高开发和运维效率。
50 20
|
1月前
|
数据采集 供应链 API
Python爬虫与1688图片搜索API接口:深度解析与显著收益
在电子商务领域,数据是驱动业务决策的核心。阿里巴巴旗下的1688平台作为全球领先的B2B市场,提供了丰富的API接口,特别是图片搜索API(`item_search_img`),允许开发者通过上传图片搜索相似商品。本文介绍如何结合Python爬虫技术高效利用该接口,提升搜索效率和用户体验,助力企业实现自动化商品搜索、库存管理优化、竞品监控与定价策略调整等,显著提高运营效率和市场竞争力。
74 3
|
2月前
|
数据采集 JSON API
如何利用Python爬虫淘宝商品详情高级版(item_get_pro)API接口及返回值解析说明
本文介绍了如何利用Python爬虫技术调用淘宝商品详情高级版API接口(item_get_pro),获取商品的详细信息,包括标题、价格、销量等。文章涵盖了环境准备、API权限申请、请求构建和返回值解析等内容,强调了数据获取的合规性和安全性。
|
2月前
|
数据挖掘 vr&ar C++
让UE自动运行Python脚本:实现与实例解析
本文介绍如何配置Unreal Engine(UE)以自动运行Python脚本,提高开发效率。通过安装Python、配置UE环境及使用第三方插件,实现Python与UE的集成。结合蓝图和C++示例,展示自动化任务处理、关卡生成及数据分析等应用场景。
161 5
|
2月前
|
存储 缓存 Python
Python中的装饰器深度解析与实践
在Python的世界里,装饰器如同一位神秘的魔法师,它拥有改变函数行为的能力。本文将揭开装饰器的神秘面纱,通过直观的代码示例,引导你理解其工作原理,并掌握如何在实际项目中灵活运用这一强大的工具。从基础到进阶,我们将一起探索装饰器的魅力所在。
|
2月前
|
Android开发 开发者 Python
通过标签清理微信好友:Python自动化脚本解析
微信已成为日常生活中的重要社交工具,但随着使用时间增长,好友列表可能变得臃肿。本文介绍了一个基于 Python 的自动化脚本,利用 `uiautomator2` 库,通过模拟用户操作实现根据标签批量清理微信好友的功能。脚本包括环境准备、类定义、方法实现等部分,详细解析了如何通过标签筛选并删除好友,适合需要批量管理微信好友的用户。
97 7
|
3月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
128 2
|
2月前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
2月前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析