Python_7_列表解析式-生成器

1. 解析式

  从一个问题来看解析式,现有如下需求:生成一个列表,元素 0-9,对每一个元素自增 1 后求平方返回新列表。

lst = list(range(10))
lst2 =[]
for value in lst:
    lst2.append((value + 1) ** 2)
print(lst2)
# 打印结果:[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

  看起来很容易理解,但是这种需求竟然用了 5 行代码!下面来看一下列表解析式的写法。

[(x+1)**2 for x in range(10)]

上面的代码功能一样,但看起来非常简洁,属于 Python 的风格 (Pythonic)!

  再来看一下,什么是列表解析式?在 Python 中列表解析式是一种语法糖,虽然对看似复杂的代码进行了简写,但是编译器会进行优化,不会因为简写而影响效率,反而因为优化提高了效率。另外还节省了代码量,减少了出错的机会,简化代码的同时增加了代码可读性。

2. 列表解析式

  列表解析式的基本语法如下:

[返回值 for 元素 in 可迭代对象 if 条件]
  • 使用中括号 [] 将表达式(推导式)括起来
  • 内部是 for 循环if 条件可选,可以有多个 if 但是不支持 elif 语句
  • 返回一个新的列表

  有这样的赋值语句 newlist = [print(i) for i in range(10)],请问 newlist 打印出来是什么?

In : newlist = [print(i) for i in range(10)]
0
1
2
3
4
5
6
7
8
9

In : newlist 
Out:[None, None, None, None, None, None, None, None, None, None]

  为什么是 None?因为表达式只会将函数的返回值作为结果,进行添加,所以当返回值是一个函数操作的对象时,一定要注意函数的返回值!

2.1. 列表解析式进阶

  有的时候我们的代码需要进行两个或多个循环,列表解析式进阶版本可以满足这种需求。它的语法是:

# 语法一:
[返回值 for 元素 in 可迭代对象 if 条件表达式1 if 条件表达式2 ...]
# 等同于:
for 元素 in 可迭代对象:
    if 条件表达式1:
        if 条件表达式2:
返回值

# 语法二:
[返回值 for 元素1 in 可迭代对象1 for 元素2 in 可迭代对象2 ...]
# 等同于:
for 元素1 in 可迭代对象1:
    for 元素2 in 可迭代对象2:
        # if 也可以加条件判断
        返回值[1 个或多个]
  • 条件表达式可以是多个,但是不能是 elif,多个 if 是并且的关系
  • 多个循环条件等同于循环嵌套,时间复杂度是 O(n * 内层循环个数)

  例子:

# 20 以内,既能被 2 整除,又能被 3 整除的列表
In : [i for i in range(20) if i % 2 == 0 if i % 3 == 0]
Out: [0, 6, 12, 18]

# 20 以内,当 i 小于 3 时,j 大于 18 时,组成一个元组返回
In : [(i,j) for i in range(20) for j in range(20) if i < 3 if j > 18]
Out: [(0, 19), (1, 19), (2, 19)]

3. 其他解析式

  除了列表解析式以外,Python 中还存在 集合解析式字典解析式生成器表达式

3.1. 集合表达式

  • 语法:{返回值 for 元素 in 可迭代对象 if 条件}
  • 列表解析式的中括号换成大括号 {} 即可
  • 同样是立即返回一个集合
# 20 以内,既能被 2 整除,又能被 3 整除的集合
In : {i for i in range(20) if i % 2 == 0 if i % 3 == 0}
Out: {0, 6, 12, 18}

  注意集合的特性,如果生成了不可 hash 的元素比如 list,那么是不能生成集合的哦,如果元素重复,集合会去重的。

3.2. 字典解析式

  • 语法:{返回键值对(key:value) for 元素 in 可迭代对象 if 条件 }
  • 列表解析式的中括号换成大括号 {} 即可
  • 请使用 key:value 键值对格式
  • 立即返回一个字典
# 生成一个 key 为 abcded 的字典
In : {x:y for x in 'abcdef' for y in range(10)}
Out: {'a': 9, 'b': 9, 'c': 9, 'd': 9, 'e': 9, 'f': 9}

  注意字典的 key 相同时,后面的赋值会把之前的值覆盖哦,所以结果是{'a': 9, 'b': 9, 'c': 9, 'd': 9, 'e': 9, 'f': 9}

4. 生成器表达式

  为什么没有元组表达式呢?因为小括号给了一个更重要的表达式使用,那就是生成器表达式,什么是生成器表达式呢?
  生成器表达式是按需计算(或者惰性求值、延迟计算)的,只有需要的时候才计算值,而列表解析式是直接返回一个新的列表,生成器是一个 可迭代对象迭代器。在使用 type 命令判断对象类型时,generator 就表示一个生成器对象。

  • 语法:(返回值 for 元素 in 可迭代对象 if 条件表达式)
  • 列表解析式的中括号换成大括号 () 即可
  • 延迟计算(惰性计算)
  • 只能迭代一次,不能回头
In : g =((i,j) for i in range(10) for j in range(20) if i<3 if j>18)

In : print(g, type(g))
<generator object <genexpr> at 0x7fe4d04a0c50> <class 'generator'>

In : for i in g: 
   ...:     print(i) 
   ...:
(0, 19)
(1, 19)
(2, 19)
# 只能迭代一次,迭代完毕生成器就为空了哦,

4.1. 特点

  注意,用括号括起来的并不是元组表达式,而变成了 生成器表达式,它本身由于惰性计算的特性和其他解析式有很多不同的特性

  1. 计算方式
      生成器表达式延迟计算(惰性计算),只有你去向它要,它才会给你计算,而列表解析式在你执行后,会直接给你生成一个新的列表。
  2. 内存占用
      生成器没有数据,内存占用极少,它在使用时一个一个地返回数据,如果将这些返回的数据合起来占用的空间也和列表解析式差不多,但是它不是立即需要这么多空间。
  3. 计算速度
      单从计算时间来看,生成器表达式耗时非常短,列表解析式时长,因为生成器本身并没有任何返回值,只是返回了一个生成器对象,列表解析式构造并返回了一个新的列表,所以看起来更耗时了
  4. 遍历
      当我们需要对数据进行遍历时,由于生成器是遍历一次计算一个返给你,而列表解析式执行完毕后直接返回一个新的列表不需要计算,所以性能要优于生成器表达式。

4.2. next 函数

  除了遍历,我们还可以通过 next 方法 来一次一次地获取生成器的数据

In : g = ((i,j) for i in range(10) for j in range(20) if i<3 if j>18)

In : next(g) 
Out:(0, 19)

In : next(g)
Out:(1, 19)

In : next(g)
Out:(2, 19)

In : next(g)
---------------------------------------------------------------------------
StopIteration     Traceback(most recent call last)
<ipython-input-12-e734f8aca5ac> in <module>
----> 1 next(g)

StopIteration: 

In :

  next()可以理解为向生成器要一次数据(拨一下生成器),当生成器为空时,就会提示 StopIteration 异常,for 循环帮我们对 StopIteration 异常做了处理,还没有学习异常处理的我们,该怎么办呢?其实next 方法为我们提供了默认值参数,即从生成器中拿不到数据,就返回指定的默认值:next(g[, default])

In : g =((i,j) for i in range(10) for j in range(20) if i <3 if j> 18)

In : next(g, 'None')
Out:(0, 19)

In : next(g, 'None')
Out:(1, 19)

In : next(g, 'None')
Out:(2, 19)

In : next(g, 'None')     # 生成器空了,就返回 default 指定的默认值 
Out: 'None'

In : next(g, 'None')
Out: 'None'

5. 总结

  Python2 引入列表解析式,Python2.4 引入生成器表达式,Python3 引入集合、字典解析式,并迁移到了 Python 2.7,一般来说,应该多用解析式,简短、高效,不过还需要注意的是:

  • 如果一个解析式非常复杂,难以读懂,可以考虑拆成 for 循环,没必要非要往列表解析式上靠
  • 生成器和迭代器是不同的对象,但都是可迭代对象
  • 可迭代对象范围更大,都可以使用 for 循环遍历

  从是否可迭代来看生成器、迭代器、可迭代对象的关系是如下

Iterable.png

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容