一、推导式

1. 推导式介绍

Python中有一种特有的语法,就是推导式(又称为解析式)。推导式是可以从一个数据序列构建另一个新的数据序列的结构体。 共有三种推导:

  • 列表(list)推导式
  • 字典(dict)推导式
  • 集合(set)推导式

2. 列表推导式

(list comprehension)

1. 基本语法

[out_express for out_express in input_list]

  • 列表推导式是Python构建列表(list)的一种快捷方式,可以使用简洁的代码就创建出一个列表
  • for循环有非常广的应用场景,也可以用来创建一个列表,而列表推导式就相当于for循环创建列表的简化版 ```python

    for循环

    list_a = list() for a in range(5): list_a.append(a) print(list_a)

    out [0, 1, 2, 3, 4]

列表推导式

list_b = [b for b in range(5)] print(list_b)

out [0, 1, 2, 3, 4]

  1. <a name="qwEac"></a>
  2. #### 2. 嵌套列表推导式
  3. ```python
  4. # in后面跟其他可迭代对象,如字符串
  5. list_c = [7 * c for c in "python"]
  6. print(list_c)
  7. # 带if条件语句的列表推导式
  8. list_d = [d for d in range(6) if d % 2 != 0]
  9. print(list_d)
  10. # 多个for循环
  11. list_e = [(e, f * f) for e in range(3) for f in range(5, 15, 5)]
  12. print(list_e)
  13. # 嵌套列表推导式,多个并列条件
  14. list_g = [[x for x in range(g - 3, g)] for g in range(22) if g % 3 == 0 and g != 0]
  15. print(list_g)
  • 运行结果

    1. ['ppppppp', 'yyyyyyy', 'ttttttt', 'hhhhhhh', 'ooooooo', 'nnnnnnn']
    2. [1, 3, 5]
    3. [(0, 25), (0, 100), (1, 25), (1, 100), (2, 25), (2, 100)]
    4. [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11], [12, 13, 14], [15, 16, 17], [18, 19, 20]]

    **

  • 代码解释

  1. 列表推导式会遍历后面的可迭代对象,然后按照for前的表达式进行运算,生成最终的列表
  2. 如果有if条件语句,for遍历后紧跟着进行条件判断
  3. 如果有多个for循环,则最终的数据数量为多个for循环的笛卡尔积
  4. 可以进行嵌套的列表推导,与嵌套for循环的原理相同

3. 代码可读性

对于不熟悉列表推导式的人来说,可以使用for循环来实现相同的功能,但是需要好几行代码,而列表推导式只需要一行代码。

  • 很多人会说代码简洁了但可读性降低了,其实不然。当我们对列表推导式熟悉(自己写几次就熟悉了),代码的功能一眼就能轻松地看出来,但是for循环代码基本不可能一眼看完,尤其当创建列表的for循环嵌套在业务逻辑的其他for循环里时,恰恰是使用列表推导式的可读性最高。
  • 同时,在Python3中列表推导式有自己的局部作用域,就像函数似的,表达式内部的变量和赋值只在局部起作用,表达式的上下文里的同名变量还可以被正常引用,局部变量并不会影响到它们。也就是说,列表推导不会有变量泄漏的问题,在列表推导中的赋值操作不可能会影响到列表推导上下文中的同名变量。

当然,也不是所有场景都推荐使用列表推导式。
比如说:如果列表推导的代码超过了两行,就要考虑改成用for循环了。超过了两行的列表推导式就真的没有可读性了。通常的原则是,只用列表推导来创建新的列表,并且尽量保持简短。

练习:

  • 过滤掉该列表 names = [“Ellen”,”Qian”,”Amy”,”Wendy”,”Tom”,”Bob”]长度小于或等于 3 的字符串列表,并将剩下的转换成大写字母。
  1. names = ["Ellen","Qian","Amy","Wendy","Tom","Bob"]
  2. print([i.upper() for i in names if len(i)<=3])
  3. #out
  4. ['AMY', 'TOM', 'BOB']
  • 求(x,y)其中 x 是 0-5 之间的偶数,y 是 0-5 之间的奇数组成的元祖列表。效果如下:[(0, 1),(0, 3),(2, 1),(2, 3),(4, 1),(4, 3)]
  1. print([(i,j) for i in range(0,5) for j in range(0,5) if i%2 == 0 and j%2==1])
  2. print([(i,j) for i in range(0,5,2) for j in range(1,5,2)])
  3. #out
  4. [(0, 1), (0, 3), (2, 1), (2, 3), (4, 1), (4, 3)]
  • tu = (i for i in range(20))这是元组推导式吗?
  1. tu = (i for i in range(20))
  2. print(type(tu))
  3. #out
  4. <class 'generator'>
  • [ ‘Bob’, ‘JOHN’, ‘alice’, ‘bob’, ‘ALICE’, ‘James’, ‘Bob’,‘JAMES’,‘jAMeS’ ]该列表很紊乱,实现去重,以及将名字格式统一成首字母大写(str.capitalize())
    1. print(list(set([i.capitalize() for i in li])))
    2. #out
    3. ['James', 'Bob', 'Alice', 'John']

3. 字典推导式

1. 基本语法

{out_exp_key: out_exp_value for out_exp in input_list}
自Python2.7以来,列表推导概念就移植到了字典上,从而有了字典推导(后面还会看到集合推导)。

2. 常用方法

  • 如果列表推导的概念已经为你所熟知,接受字典推导并不难.直接看代码吧

    1. # 因为key是唯一的,所以最后value都是1
    2. dict_a = {key: value for key in 'python' for value in range(2)}
    3. print(dict_a)
    4. # 可以根据键来构造值
    5. dict_b = {key: key * key for key in range(6)}
    6. print(dict_b)
    7. # 遍历一个有键值关系的可迭代对象
    8. list_phone = [('HUAWEI', '华为'), ('MI', '小米'), ('OPPO', 'OPPO'), ('VIVO', 'VIVO')]
    9. dict_c = {key: value for key, value in list_phone}
    10. print(dict_c)
    11. # 快速将字典中的k,v互换
    12. dic1 = {0:"0",1:"1",2:"2"}
    13. dic2 = {v:k for k,v in dic1.items()}
    14. print(dic2)
  • 运行结果

    1. {'p': 1, 'y': 1, 't': 1, 'h': 1, 'o': 1, 'n': 1}
    2. {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
    3. {'HUAWEI': '华为', 'MI': '小米', 'OPPO': 'OPPO', 'VIVO': 'VIVO'}
    4. {'0': 0, '1': 1, '2': 2}

练习:

  • student = {“name”:”OldZhang”,”age”:”22”,”gender”:”male”}将key,value 互换
  1. print({v:k for k,v in student.items()})
  2. #out
  3. {'OldZhang': 'name', '22': 'age', 'male': 'gender'}
  • li = [“Ellen”,”Qian”,”Amy”,”Wendy”,”Tom”,”Bob”]将该列表实现{0: ‘Ellen’,1: Qian, 2: ‘Amy’, 3: ‘Wendy’, 4: ‘Tom’, 5: ‘Bob’}注意:此处可用到enumerate方法(自行学习)
  1. li = ["Ellen","Qian","Amy","Wendy","Tom","Bob"]
  2. #方法1:
  3. li1 = []
  4. for i,j in enumerate(li):
  5. li1.append((i,j))
  6. print(li1)
  7. #方法2:
  8. print([(i,j) for i,j in enumerate(li) ])
  9. #out
  10. [(0, 'Ellen'), (1, 'Qian'), (2, 'Amy'), (3, 'Wendy'), (4, 'Tom'), (5, 'Bob')]

4. 集合推导式

1. 基本语法

{out_exp_res for out_exp in input_set}
Python2.7不仅带来了字典推导式,还带来了集合推导式,同样我们直接看代码,这样可以直观的看到集合推导式

  1. # 遍历一个可迭代对象生成集合
  2. set_a = {value for value in '有人云淡风轻,有人负重前行'}
  3. print(set_a)

**

  • 运行结果:
    1. {'负', '有', '人', '轻', '前', '云', '重', ',', '淡', '风', '行'}

集合是无序且不重复的,所以会自动去掉重复的元素,并且每次运行显示的顺序不一样。

5. 推导式小结

  • 集合推导式就是将列表推导式的[]换成{}
  • 字典推导式就是推导出两个值并构建成键值对的样子

另外,不管是字典推导式还是集合推导式,后面都可以像列表推导式一样接if条件语句、嵌套循环等。

———————————————— 版权声明:本文为CSDN博主「Python碎片」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/weixin_43790276/article/details/90247423

二、迭代器

1. 定义

迭代器指的是迭代取值的工具,迭代是指一个重复的过程,每一次重复都是基于上一次结果而来迭代提供了一种通用的不依赖索引的迭代取值方式。

2. 可迭代对象

可以用for循环遍历的对象都是可迭代对象。

  • str,list,tuple,dict,set等都是可迭代对象。
  • generator,包括生成器和带yield的生成器函数。

3. 判断是否可迭代

除了看内置是否含有iter方法来判断该对象是否是一个可迭代的对象之外,我们还可以使用 isinstance() 判断一个对象是否是 Iterable 对象
• isinstance()—>用来判断对象是否是相应类型,与type()类似

  1. from collections import Iterable
  2. print(isinstance('abc',Iterable))
  3. print(isinstance([1,2,3],Iterable))
  4. print(isinstance((1,2,3),Iterable))
  5. print(isinstance({1,2,3},Iterable))
  6. print(isinstance({'money':123},Iterable))
  7. #out
  8. True
  9. True
  10. True
  11. True
  12. True

4. 迭代对象

  • 有内置的next()方法的对象,执行该方法可以不依赖索引取值
  • 有内置的iter()方法的对象,执行迭代器的iter()方法得到的依然是迭代器本身

需要注意的是,可迭代对象不一定是迭代器。

  1. from collections import Iterable,Iterator
  2. li = [1,2,3,4]
  3. print(isinstance(li,Iterator)) # False

5. iter()

可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。我们可以通过iter()方法将可迭代的对象,转为迭代器。

  1. li = [1,2,3,4]
  2. lis = iter(li)
  3. print(type(lis)) # <class 'list_iterator'>

注意:

  • 迭代器不可以通过下标取值,而是使用next()或者next()。但是只要超出范围则直接报错StopIteration。
  • next()只能顺延调用,不能往前。

5. 可迭代对象与迭代器区别

  • 可用于for循环的都是可迭代类型
  • 作用于next()都是迭代器类型
  • list、dict、str等都是可迭代对象的但不是迭代器,因为next()函数无法调用它们。可以通过iter()函数将它们转为迭代器
  • Python的for循环本质就是通过不断调用next()函数实现的。

三、生成器

1. 生成器定义

在Python中,一边循环一边计算的机制,称为生成器:generator。

2. 生成器存在的原因

列表所有数据都在内存中,如果有海量数据的话会非常消耗内存。
比如说:我们仅仅需要访问前面几个元素,但后面绝大多元素占用的内存就会浪费了。
那么生成器就是在循环的过程中根据算法不断推算出后续的元素,这样就不用创建整个完整的列表,从而节省大量的空间。
总而言之,就是当我们想要使用庞大数据,又想让它占用的空间少,那就使用生成器。
生**成器最佳应用场景是:你不想同一时间将所有计算出来的大量结果集分配到内存当中,特别是结果里还包含循环。**

3. 如何创建生成器

1. 生成器表达式

生成器表达式来源于迭代和列表解析的组合,生成器和列表解析类似,但是它使用()而不是[]。

  1. g = (x for x in range(5))
  2. print(g) # generator object
  3. print(next(g))
  4. print(next(g))
  5. print(next(g))
  6. print(next(g))
  7. print(next(g))
  8. # 超出报错
  9. print(next(g))
  10. for i in g:
  11. print(i)

2. 生成器函数

当一个函数中包含yield关键字,那么这个函数就不再是一个普通的函数,而是一个generator。调用函数就是创建了一个生成器对象。其工作原理就是通过重复调用next()或者next()方法,直到捕获一个异常。
例如:要实现输入数字N,即可返回长度为N的递增列表

  1. #方法1:
  2. n = int(input('数字: '))
  3. def test(n):
  4. a = 0
  5. li =[]
  6. while a<n:
  7. li.append(a)
  8. a += 1
  9. print(li)
  10. test(n)
  11. #out
  12. 数字: 5
  13. [0, 1, 2, 3, 4]
  14. #方法2:
  15. n = int(input('数字: '))
  16. def yieldtest(n):
  17. a = 0
  18. while a<n:
  19. yield a
  20. a += 1
  21. res =yieldtest(n)
  22. print(next(res))
  23. print(next(res))
  24. print(next(res))
  25. print(next(res))
  26. print(next(res))
  27. #out
  28. 数字: 1000000
  29. 0
  30. 1
  31. 2
  32. 3
  33. 4

注意:
yield返回一个值,并且记住这个返回值的位置,下次遇到next()调用时,代码从yield的下一条语句开始执行。与return的差别是,return也是返回一个值,但是直接结束函数。

  • 比如:实现斐波那契数列,除第一个和第二个数外,任何一个数都可以由前两个相加得到:1,1,2,3,5,8,12,21,34….. ```python def createNums(): print(“——-func start——-“) a,b = 0,1 for i in range(5):
    1. # print(b)
    2. print("--1--")
    3. yield b
    4. print("--2--")
    5. a,b = b,a+b
    6. print("--3--")
    print(“——-func end——-“)

g = createNums() print(next(g))
print(next(g))
print(next(g)) print(next(g)) print(next(g))

  1. <a name="hm8Oz"></a>
  2. #### 3. send()
  3. send() 和next()一样,都能让生成器继续往下走一步(遇到yield返回),但send()能传一个值,这个值作为yield表达式整体的结果。
  4. ```python
  5. def test():
  6. a1 = yield "hello"
  7. print("---1---")
  8. yield a1
  9. res = test()
  10. print(next(res)) # "hello"
  11. print(res.send("world")) # "world"

也就是说,通过send方法可以强行修改上一个yield表达式值。
比如函数中有一个yield赋值,a1 = yield “hello”,第一次迭代到这里会返回”hello”,但是a1还没进行赋值。第二次迭代时,使用.send(“world”),那么,就是相当于强行修改yield “hello”表达式的值为”world”,所以yield a1结果为”world”。

4. 迭代器与生成器的区别

  • 生成器能做到迭代器能做的所有事
  • 而且因为生成器自动创建了iter()和next()方法,生成器显得简洁,而且高效。