一. 列表推导式
作用:用一个表达式创建一个有规律的列表或控制一个有规律列表。
列表推导式又叫列表生成式。
1.1 快速体验
需求:创建一个0-10的列表。
- while循环实现
# 1. 准备一个空列表
list1 = []
# 2. 书写循环,依次追加数字到空列表list1中
i = 0
while i < 10:
list1.append(i)
i += 1
print(list1)
- for循环实现
list1 = []
for i in range(10):
list1.append(i)
print(list1)
- 列表推导式实现
list1 = [i for i in range(10)]
print(list1)
1.2 带if的列表推导式
需求:创建0-10的偶数列表
- 方法一:range()步长实现
list1 = [i for i in range(0, 10, 2)]
print(list1)
- 方法二:if实现
list1 = [i for i in range(10) if i % 2 == 0]
print(list1)
- 方法三: for循环加if实
list1 = []
for i in range(10):
if i % 2 == 0:
list2.append(i)
print(list2)
通过对比可以发现,生成同样内容的列表,列表生成式的方法所使用的代码更少,更加简洁,让代码看起来更加的清爽,而这还不是列表生成式的唯一优点。
1.3 多个for循环实现列表推导式
需求:创建列表如下:
[(1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
代码如下:
list1 = [(i, j) for i in range(1, 3) for j in range(3)]
print(list1)
二. 字典推导式
思考:如果有如下两个列表:
list1 = ['name', 'age', 'gender']
list2 = ['Tom', 20, 'man']
如何快速合并为一个字典?
答:字典推导式
字典推导式作用:快速合并列表为字典或提取字典中目标数据。
2.1 快速体验
1.创建一个字典:字典key是1-5数字,value是这个数字的2次方。
dict1 = {i: i**2 for i in range(1, 5)}
print(dict1) # {1: 1, 2: 4, 3: 9, 4: 16}
2.将两个列表合并为一个字典
list1 = ['name', 'age', 'gender']
list2 = ['Tom', 20, 'man']
dict1 = {list1[i]: list2[i] for i in range(len(list1))}
print(dict1)
注意:如果两个数据个数相同,len统计任何一个列表的长度都可以。如果两个列表数据个数不同,len统计数据多的列表数据个数会报错;len统计数据少的列表数据个数不会报3
3.提取字典中目标数据
counts = {'MBP': 268, 'HP': 125, 'DELL': 201, 'Lenovo': 199, 'acer': 99}
# 需求:提取上述电脑数量大于等于200的字典数据
count1 = {key: value for key, value in counts.items() if value >= 200}
print(count1) # {'MBP': 268, 'DELL': 201}
筛选key-value对的条件要放到后面,这是语法上的要求。
三. 集合推导式
需求:创建一个集合,数据为下方列表的2次方。
3.1 快速体验
list1 = [1, 1, 2]
代码如下:
list1 = [1, 1, 2]
set1 = {i ** 2 for i in list1}
print(set1) # {1, 4}
注意:集合有数据去重功能。
四、生成器
4.1 提供生成器的两种方式
python有两种方式提供生成器
-
生成器表达式生成器
-
函数生成器函数
4.1.1 列表推导式生成器表达式
new_list = [x * 3 for x in range(21)]
print(type(new_list)) # <class 'list'>
g = (x * 3 for x in range(21))
print(type(g)) # <class 'generator'>
4.1.2 调用生成器的方法
4.1.2.1 方式一 调用 next() 得到元素
g = (x * 3 for x in range(21))
print(type(g)) # <class 'generator'>
print(g.__next__()) # 0
print(g.__next__()) # 3
4.1.2.2 方式二 通过next()
g = (x * 3 for x in range(21))
print(type(g)) # <class 'generator'>
print(next(g)) # 0
print(next(g)) # 3
4.1.3 函数生成器
如果一个函数内部出现了yield这个关键字,那么该函数就是一个生成器函数,调用生成器函数将得到一个生成器,下面的示例将演示一个生成器函数的定义和使用:
# coding=utf-8
# 定义一个生成器函数
def my_generator(n):
index = 0
while index < n:
yield index
index += 1
generate = my_generator(5)
print(type(generate))
for i in generate:
print(i)
程序输出结果为:
<class 'generator'>
0
1
2
3
4
理解上面的代码,要注意以下几点:
-
当执行generate = my_generator(5) 这行代码时,my_generator函数里的代码没有被执行,因为函数里有yield关键,函数已经变成了一个生成器函数,生成器函数在被调用时会返回一个生成器,此时,函数里的代码不会被执行。
-
for循环的过程就是执行next方法的过程,当生成器的next方法被调用时,函数内部的代码才会执行,执行过程中遇到yield关键字,就会暂停(挂起),并将yield的参数做此次next方法的返回值。
-
随着next方法的执行,函数内的while循环终究会有停止的时候,这个时候函数结束了,抛出StopIteration异常。
-
生成器每次遇到yield暂停执行时,会把函数内的所有变量封存在生成器中,当下一次next方法被执行时,恢复这些变量。
-
生成器函数内部,不允许使用return语句返回任何值,因为生成器函数已经默认返回一个生成器了,但是你可以只写一个return,后面不带任何值。
4.2 生成器有哪些优点
生成器是为延迟计算提供了支持,只在需要的时候再进行计算,这样一来,就能够减少对内存的使用。接下来将用一个例子来讲解生成器的好处。
已知有两个字典,内容如下:
dict_1 = {
'key1': [1, 2, 3],
'key2': [2, 3, 4],
'key3': [3, 4, 5]
}
dict_2 = {
'key1': ['a', 'b', 'c'],
'key2': ['b', 'c', 'd'],
'key3': ['c', 'd', 'e']
}
可以看到两个字典有相同的key,且value都是列表,由于篇幅有限,我让每个字典都只有3个key,但其实它可以拥有更多的key,value即列表里也可以拥有更多的元素,这样的假设是希望你能明白,更多的元素要占用更多的内存。
现在,我想知道两个字典合并后每个key对应的value是什么内容,并且对它进行输出,我先来实现一个耗费内存的版本,示例1 代码如下:
dict_1 = {
'key1': [1, 2, 3],
'key2': [2, 3, 4],
'key3': [3, 4, 5]
}
dict_2 = {
'key1': ['a', 'b', 'c'],
'key2': ['b', 'c', 'd'],
'key3': ['c', 'd', 'e']
}
def merge_dict(dict_1, dict_2):
merge_dit = {}
merge_dit.update(dict_1)
for k, v in merge_dit.items():
merge_dit[k].extend(dict_2[k])
return merge_dit
merge_dit = merge_dict(dict_1, dict_2)
for k, v in merge_dit.items():
print(k,v)
程序输出结果为:
key1 [1, 2, 3, 'a', 'b', 'c']
key2 [2, 3, 4, 'b', 'c', 'd']
key3 [3, 4, 5, 'c', 'd', 'e']
之所以说这个版本的实现是耗费内存的,是因为merge_dict函数创建了一个新的字典用来保存合并后的结果,理论上内存的使用增加了一倍,接下来看不节省内存的版本,示例2代码如下:
dict_1 = {
'key1': [1, 2, 3],
'key2': [2, 3, 4],
'key3': [3, 4, 5]
}
dict_2 = {
'key1': ['a', 'b', 'c'],
'key2': ['b', 'c', 'd'],
'key3': ['c', 'd', 'e']
}
for key, value in dict_1.items():
value.extend(dict_2[key])
print(key, value)
这个版本的实现的确在内存使用上比示例1好很多,但是却有一些不易被发现的缺陷
我现在的要求是输出合并后的key和value,但是如果出现了新的要求,计算value中的数字之和,那么就需要写一段新的代码,例如下面这样
for key, value in dict_1.items():
value.extend(dict_2[key])
sum = 0
for item in value:
if isinstance(item, int):
sum += item
print(key, sum)
对合并后结果的使用逻辑必须和合并的逻辑放在一起,就是说在for循环内部,既要完成字典合并又要完成结果的输出或者数字之和的计算
其他函数或者模块无法直接使用合并后的结果
对于第3条,如果采用示例1中的办法,其他函数可以直接使用新创建的字典,但这样耗费内存,采用示例2中的办法,虽然不耗费内存,但是必须直接对两个字典进行操作,而且还要关心如何去合并,那么有没有什么好的办法,既能不耗费内存,又能让代码工整简洁,利于维护呢,请看示例3
# coding=utf-8
dict_1 = {
'key1': [1, 2, 3],
'key2': [2, 3, 4],
'key3': [3, 4, 5]
}
dict_2 = {
'key1': ['a', 'b', 'c'],
'key2': ['b', 'c', 'd'],
'key3': ['c', 'd', 'e']
}
def gen_dict(dict_1, dict_2):
"""
gen_dict 封装了合并两个字典的细节,而且不耗费内存
:param dict_1:
:param dict_2:
:return:
"""
for key, value in dict_1.items():
value.extend(dict_2[key])
yield key, value
# generate 是一个生成器,你在使用时根本不需要关心两个字典是如何合并的
generate_1 = gen_dict(dict_1, dict_2)
for key, value in generate_1:
print(key, value)
# 计算合并后每个key所对应的value内部元素之和
generate_2 = gen_dict(dict_1, dict_2)
for key, value in generate_2:
sum = 0
for item in value:
if isinstance(item, int):
sum += item
print(key, sum)
程序输出结果为:
('key3', [3, 4, 5, 'c', 'd', 'e'])
('key2', [2, 3, 4, 'b', 'c', 'd'])
('key1', [1, 2, 3, 'a', 'b', 'c'])
('key3', 12)
('key2', 9)
('key1', 6)
看到示例3的代码,是不是感到清爽许多,函数gen_dict返回一个生成器,该函数实现了对两个字典的合并而且不耗费内存。
在使用生成器generate_2时,你根本不需要关心两个字典是如何合并的,你只需要关心如何计算数字之和,合并字典的逻辑与合并后结果的处理逻辑是完全可以分开的,不必像示例2那样纠缠在一起。
4.3 使用生成器需要避免的坑
生成器虽然好用,但如果使用不当就会引发问题。
def generate_num(n):
for i in range(n):
yield n
iter_num = generate_num(10)
for num in iter_num:
print(num)
for num in iter_num: # 第二次执行for循环,没有任何效果
print(num)
for循环的过程就是执行next方法的过程,经历过一次for循环以后,迭代器已经到了末尾,在for循环的内部,已经抛出StopIteration异常,for循环在捕捉到这个异常后停止遍历,因此第二次for循环时不会产生任何效果。
五、三个实例带你了解生成器的作用
python的生成器究竟有什么作用? 本文通过3个具体的例子向你阐述生成器的作用, 1. 使用生成器非常便利的结束两层for循环, 2. 一个for循环遍历多个列表, 3. 并行遍历多个可迭代对象
提到生成器,你可能会有一个简单的概念,如果函数里使用yield关键字,那么这个函数就是一个生成器,不同于return,生成器使用yield来返回值。
令人感到困惑之处在于,似乎yield和return 没啥区别,但实质上区别非常大,最明显之处便在于,return 语句执行后,函数就退出了,而yield语句执行时,仅仅是返回一个值而已,不存在函数结束这个概念,因此生成器都要结合for循环进行使用。
下面通过几个示例,来向你阐释生成器的作用
5.1 使用生成器非常便利的结束两层for循环
两层循环过程中,如果想通过break终止循环,是一件简单,但却很麻烦的事情,例如下面的这段代码
lst1 = [1, 2, 4, 5, 6]
lst2 = [1, 5, 6, 8, 9]
stop = False
for i in lst1:
for j in lst2:
if i + j == 10:
stop = True
break
if stop:
break
两层for循环的目的非常简单,从两个列表中各找出一个数,使得他们的和等于10,而且只需找出一个组合即可。
找到满足要求的组合后,为避免不必要的循环,需要终止循环,而此时,if条件语句在for循环的最里层,此处执行break,只能跳出最里层的for循环,想要终止最外层的for循环,就必须传递终止信号给它,代码里,通过stop=True,告知外层for循环可以终止了。
这样的代码写起来,显然有些繁琐,最后的if stop判断总显得多余,面对这种情况,可以巧妙的利用生成器来避免这种复杂的写法。
lst1 = [1, 2, 4, 5, 6]
lst2 = [1, 5, 6, 8, 9]
def num_generator(lst1, lst2):
for i in lst1:
for j in lst2:
yield i, j
for i, j in num_generator(lst1, lst2):
if i + j == 10:
print(i, j)
break
生成器num_generator里通过两层for循环对数据进行遍历,真正的业务逻辑使用一个for循环,这里就避免了跳出两层for循环的困境,这一次break,结束了for循环对num_generator的使用。
试想一下,如果没有生成器这种技术,有什么办法能实现一个break跳出两层for循环么? 这种技术还可以扩展到更多层的for循环。
5.2 chain–一个for循环遍历多个列表
有这样一种应用场景,你需要遍历多个列表来执行某个操作,比如下面两个列表,你需要找出列表里所有的偶数
lst1 = [1, 2, 4, 5, 6]
lst2 = [1, 5, 6, 8, 9]
5.2.1 嵌套循环
一种直接的方法是使用多层for循环
lst1 = [1, 2, 4, 5, 6]
lst2 = [1, 5, 6, 8, 9]
for lst in [lst1, lst2]:
for item in lst:
if item % 2 == 0:
print(item)
5.2.2 创建新列表
为了减少for循环的层次,你也可以创建一个新的列表,包含这两个列表里的所有数据
lst1 = [1, 2, 4, 5, 6]
lst2 = [1, 5, 6, 8, 9]
lst = [*lst1, *lst2]
for item in lst:
if item % 2 == 0:
print(item)
5.2.3 chain
上面两个方法虽然都可以满足要求,但还有更简单的方法,使用chain
from itertools import chain
lst1 = [1, 2, 4, 5, 6]
lst2 = [1, 5, 6, 8, 9]
for item in chain(lst1, lst2):
if item % 2 == 0:
print(item)
chain内部会对传入的参数逐个进行遍历,lst1 和 lst2仿佛成为一个整体,如果不使用python提供的这个chain类,我们自己可以通过生成器来实现一个效果相同的函数
lst1 = [1, 2, 4, 5, 6]
lst2 = [1, 5, 6, 8, 9]
def my_chain(*iters):
for iter_item in iters:
for item in iter_item:
yield item
for i in my_chain(lst1, lst2):
if i % 2 == 0:
print(i)
5.3 zip–并行遍历多个可迭代对象
使用chain时,是对列表逐个进行遍历,但有时,我们又希望是并行遍历,python原生的zip函数提供了这样的功能,下面是使用示例
lst1 = ['python', 'java', 'c']
lst2 = [95, 97, 98]
dic = {}
for language, score in zip(lst1, lst2):
dic[language] = score
print(dic)
两个列表,第一个列表是科目,第二个列表是分数,需要将这两个列表的内容转换为字典,使用zip函数,就可以并行遍历两个列表,接下来,我们自己使用生成器来实现一个相同功能的函数
lst1 = ['python', 'java', 'c']
lst2 = [95, 97, 98]
def my_zip(*args):
min_len = min(len(item) for item in args)
index = 0
while index < min_len:
lst = []
for iter_item in args:
lst.append(iter_item[index])
index += 1
yield tuple(lst)
info = {}
for language, score in my_zip(lst1, lst2):
info[language] = score
六、迭代器
迭代器是一个让程序员可以遍历一个容器的对象,这里有三个概念需要你深刻的理解
-
可迭代对象(Iterable)
-
迭代器(Iterator)
-
迭代(Iteration)
6.1 可迭代对象
6.1.1 直观理解
list,tuple,dict, set, str,还有生成器这样的对象,可以直接作用于for循环,这些都是可迭代对象,一个打开的file对象和socket对象也是可迭代对象。
下面的代码演示for循环如何作用于一个可迭代对象
lst = [1, 2, 3]
for item in lst:
print(item)
程序执行结果为:
1
2
3
- isinstance() 检查一个对象是否是另一个对象的实例
下面的代码,演示了如何判断一个对象是否为可迭代对象
from collections import Iterable
lst = [1, 2, 3]
print(isinstance(lst, Iterable)) # Ture
6.2 迭代器(Iterator)
6.2.1 什么是迭代器
迭代器是访问可迭代对象的工具
-
迭代器是指用iter(obj)函数返回的对象(实例)
-
实现了iter方法的对象就是可迭代对象
-
迭代器是指用next(it)函数获取可迭代对象的数据,
-
实现了 iter方法且又实现了next方法的对象就是迭代器
如果一个python对象实现 实现了iter方法的对象就是可迭代对象和 实现了 iter方法且又实现了被next()函数调用,并不断返回下一个值的对象称之为迭代器
所谓迭代,就是不停的调用迭代器的next方法的过程,直到抛出StopIteration异常
6.2.1 list 是迭代器么
list不是迭代器,因为list这个数据类型没有实现next方法
from collections import Iterable, Iterator
lst = [1, 2, 3]
# lst列表是可迭代对象
print(isinstance(lst, Iterable)) # True
# Iterator检查lst列表是不是迭代器
print(isinstance(lst, Iterator)) # False lst列表不是迭代器,因为它没有实现next方法
# iter方法会返回一个迭代器
lst_iter = iter(lst)
print(isinstance(lst_iter, Iterator)) # True
list内部实现了__getitem__, 因此内置函数iter检测到list没有实现next方法但是实现了__getitem__方法后,会创建一个迭代器,并尝试通过索引(从0开始)来获取元素。
6.2.2 迭代器不能重复使用
一个迭代器是不能重复使用的,因为迭代的本意就是从头到尾遍历某个集合,如果你已经遍历到末尾,此时想重新使用这个迭代器从到到尾来遍历就是不可行的,next方法已经走到了末尾。
示例1:
lst = [1, 2, 3]
iter_lst = iter(lst)
for i in iter_lst:
print(i)
# 这一次没有任何数据输出,因为iter_lst 作为一个迭代器,已经到达末尾
# 调用next方法只会抛出异常,被for循环捕获
for i in iter_lst:
print(i)
程序输出结果为:
1
2
3
上面的示例中,iter_lst是iter函数返回的迭代器,使用了一次之后,第二次再使用就没有任何效果了,你可能会感到困惑,并指出下面的代码有些难以理解。
示例2:
lst = [1, 2, 3]
for i in lst:
print(i)
# 两次都可以输出
for i in lst:
print(i)
程序输出结果为:
1
2
3
1
2
3
如果你对这两段代码的输出结果感到困惑,那么你需要细细的品味理解下面的内容:
示例1 中,for循环作用于iter_lst,而iter_lst 是一个迭代器,两次for循环使用的是同一个迭代器,但迭代器只能使用一次
示例2 中,for循环作用于lst,lst是一个列表,此时,python内部使用iter函数返回lst的迭代器供for循环使用,两次for循环使用的是不同的迭代器,因此两个for循环均有输出