home1.gif

一. 解析式 / 推导式 Comprehension


解析式,也叫推导式(comprehensions),它是 python 的一种独有特性。解析式是可以从一个数据序列构建另一个新的数据序列。

解析式包括:列表解析式、字典解析式、集合解析式。

1. 列表解析式 list comprehension


先来看个例子: 有个列表 lst =list(range(10)),若将 lst 每个元素自增 1后求平方,然后生成一个新列表。新列表 lst_new ?

  1. lst = list(range(10))
  2. lst_new = []
  3. for el in lst:
  4. lst_new.append((el + 1) ** 2)

而使用列表解析式,这样来写:

  1. lst = list(range(10))
  2. lst_new = [(el + 1) ** 2 for el in lst]

那列表解析式长啥样呢?

它的基本语法格式

  1. [表达式 for 元素 in 可迭代对象 if 条件]
  2. [表达式 for 元素 in 可迭代对象1 for 元素 in 可迭代对象2]
  3. ...

说明:

  1. 列表解析式,既叫列表,那就是用 [ ] 括起来的;
  2. [] 内部用 for 循环,if 条件是可选的;
  3. 返回一个新列表

列表解析式 其实是一种语法糖

  • 编译器对它作了优化,提高了效率
  • 简化了代码,增强了可读性

【实例 1】10 以内的偶数

  1. even = [i for i in range(10) if i%2==0]
  2. # 用append 函数就是
  3. even = []
  4. for x in range(10):
  5. if x % 2 == 0:
  6. even.append(x)

想一想 ↓↓

  1. one_lst = [print(i) for i in range(5)] # [None, None, None, None, None]
  2. one_lst = [i for i in range(5)] # [0, 1, 2, 3, 4]

练习一下 ↓

  1. [i for i in range(20) if i%2==0 and i%3==0]
  2. [i for i in range(20) if i%2==0 if i%3==0]

再来 ↓↓

  1. [(x, y) for x in 'abc' for y in range(1, 3)]
  2. [[x, y] for x in 'abc' for y in range(1, 3)]
  3. [{x: y} for x in 'abc' for y in range(1, 3)]
  4. # 列表解析式里有2个 for 时,等价于
  5. lst = []
  6. for i in iterable_1:
  7. for j in iterable_2:
  8. lst.append(expr)

2. 集合解析式 set comprehension


集合解析式与列表解析式非常类似,把 [ ] 改成 { } 即可。基本格式:

  1. {表达式 for 元素 in 可迭代对象 if 条件}

立即返回一个集合

【实例 2】10以内偶数的集合

  1. {x for x in range(10) if x%2==0}

3. 字典解析式 dict comprehension


字典解析式是列表解析式的延续,语法也差不多,不同的只是生成的是字典而已。

基本格式

  1. {表达式 for 元素 in 可迭代对象 if 条件}

与集合解析式不同的是,表达式是 key:value 字典形式的,立即返回一个字典。

【实例 3】字典解析式的应用

  1. {str(x):y for x in range(3) for y in range(4)} # {'0': 3, '1': 3, '2': 3}

总结:

  1. 解析式有很多优势,在不复杂的循环中建议多用;
  2. 但是,如解析式太复杂,难以读懂,建议使用 for 循环;
  3. 生成器和迭代器是不同的对象,但都是可迭代对象;

**

goon.gif

二. 生成器 Generator


生成器 generator,在 python 中它是一边循环一边计算的一种机制,它保存的不是目标数据,而是一套生成数据的算法,且不会立即执行,而是在调用它的时候才会计算并返回。

为什么要有生成器呢? 为了节省内存空间。 列表的数据是完整的放在内存中的,在数据规模非常大的时候,是很耗内存的,而如果列表中靠后的数据不会用到的话,那可能有很大的内存就白白浪费了。为了解决这个现象就有了生成器了。生成器不用创建完整的list数据,调用一个计算并返回一个,从而节省了空间。

生成器指的是生成器对象,创建生成器有两种方法:“元组”解析式(生成器表达式)、yield 关键字。

1. 生成器之“元组”解析式 *


解析式形式的基本语法格式

  1. (表达式 for 元素 in 可迭代对象 if 条件)

说明:把列表解析式的 [] 改成小括号 () 就是一个生成器;

和列表解析式的区别:

  • 生成器表达式,是按需计算(或称惰性求值、延迟计算),需要的时候才计算值
生成器(表达式) 列表解析式
g = (“{:03}”.format(i) for i in range(1,11))
next(g)
for x in g:
print(‘Loop 1: {}’.format(x))
for x in g:
print(‘Loop 2: {}’.format(x))
g = [“{:03}”.format(i) for i in range(1,11)]
print(g)
for x in g:
print(‘Loop 1: {}’.format(x))
for x in g:
print(‘Loop 2: {}’.format(x))
返回一个生成器对象(可迭代的对象) 返回一个新列表
按需计算 立即计算并返回完整值
耗时短 耗时长
返回迭代器,可迭代 返回可迭代对象列表,可迭代
遍历完一遍后不能回头再次遍历 遍历完一遍后可以再次遍历

2. 生成器之 yield


生成器也可以使用 yield 关键字得到一个生成器函数,调用这个函数就会得到生成器对象。

yield 生成器函数

函数体中包含 yield 语句的函数,返回生成器对象。一个简单的生成器函数,先看看它是怎么运作滴:

  1. def gener():
  2. yield 1
  3. yield 2
  4. yield 3
  5. g = gener()

说明:

  1. 调用生成器函数 gener() 得到生成器 g;
  2. 通过next()函数执行生成器,当执行到 yield 语句时,返回 yield 表达式的值后会暂停执行(返回 1);
  3. 当再次next()执行时,从 yield 1 下面的一句开始执行,遇到 yield 语句就返回表达式的值后暂停,这样往复执行

注意 要想使生成器函数执行,有 2 种方式:

  1. 通过生成器调用 next() 函数(或next());
  2. 通过 for 循环遍历生成器(for 底层会不断的调用 next() 函数);

实际中多数这样使用生成器:

  1. def intNum():
  2. for i in range(5):
  3. yield i
  4. print("再次调用从这里继续执行")
  5. num = intNum()

说明:上面代码中 num 生成器(对象),用 next(num) 可看到 yield 返回的结果。

小结 生成器函数:

  • 生成器函数等价于生成器表达式,简单的语句或逻辑使用生成器表达式,而过于复杂的就使用生成器函数。
  • 和普通函数不同,生成器函数的返回值用的是 yield 关键字,而不是 return。
  • 调用生成器函数时生成生成器对象,而函数体是不会立即执行的。
  • 一般的函数被调用后会立即执行完返回结果,而生成器函数通过 next() 函数可被多次执行。
  • 没有多余的 yield 语句能被执行时,如果继续调用 next() 会抛出异常。


生成器函数的应用

【实例 4】斐波那契数列,应用在递归问题中

  1. import sys
  2. def fibonacci(n): # 生成器函数 - 斐波那契
  3. a, b, counter = 0, 1, 0
  4. while True:
  5. if (counter > n):
  6. return
  7. yield a
  8. a, b = b, a + b
  9. counter += 1
  10. f = fibonacci(10) # f 是一个迭代器,由生成器返回生成
  11. while True:
  12. try:
  13. print (next(f), end=" ")
  14. except StopIteration:
  15. sys.exit()

【实例 5】计数器

  1. def inc():
  2. def counter():
  3. i = 0
  4. while True:
  5. i += 1
  6. yield i
  7. c = counter()
  8. return lambda : next(c)
  9. foo = inc()
  10. print(foo())
  11. print(foo())

yield from
**
基本语法格式:

  1. yield from iterable
  2. for item in iterable:
  3. yield item

yield from 它是由for循环而来,是它的语法糖。

  1. yield from range(1000)
  2. for x in range(1000):
  3. yield x

**

goon.gif

三. 迭代器


  1. 可迭代对象 iterable

可迭代对象 可理解成可以被 for 循环迭代的对象就是可迭代对象。其实本质上说,一个对象中有 iter() 方法,那这个对象就是可迭代对象。

  • 迭代 是访问集合元素(理解为容器类型)的一种方式。
  • 以作用于 for 循环的数据类型,可迭代对象:
    1. 集合(容器)类型:如 str、list、tuple、dict、set 等;
    2. generator 生成器

判断可迭代****
使用 isinstance() 判断一个对象是否是 iterable 对象

  1. from collections import Iterable
  2. isinstance(100, Iterable) # False
  3. isinstance('abc', Iterable) # True
  4. isinstance([1, 2, 3], Iterable) # True
  5. isinstance((4, 5, 6), Iterable) # True

注意,可迭代对象可以迭代,但未必有序,未必可索引。

2. 迭代器 iterator


迭代器 是一个可以记住遍历位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。本质上,一个可被 next() 函数调用并不断返回下一个值的对象就是 iterator。

判断迭代器?** **
使用 isinstance() 判断一个对象是否是 iterator

  1. from collections import Iterator
  2. isinstance(100, Iterator) # False
  3. isinstance('abc', Iterator) # False
  4. isinstance([1, 2, 3], Iterator) # False
  5. isinstance((4, 5, 6), Iterator) # False
  6. isinstance((i for i in range(3)), Iterator) # True

iter() 方法 通过 iter() 方法,可以把一个可迭代对象封装成迭代器

  1. from collections import Iterator
  2. isinstance(iter('abc'), Iterator) # True

注意:str、list、tuple、dict、set等是 iterable,但不是 iterator

3. 可迭代对象、迭代器、生成器的关系


image.png
从上图可以看出:

  1. 可迭代对象包含迭代器;
  2. 一个对象有 iter() 方法,就是可迭代对象;
  3. 一个可迭代对象,可被 next() 方法调用,就是迭代器;

end1.gif