05.python解析式、匿名函数lambda、可迭代对象、迭代器、生成器、生成器表达式、生成器函数、内建函数
匿名函数
- 匿名:隐藏名字,即没有名称
- 匿名函数:没有名字的函数。
- 函数没有名字该如何定义?函数没有名字如何调用?
Lambda表达式
Python中,使用Lambda表达式构建匿名函数。
lambda x: x ** 2 # 定义
(lambda x: x ** 2)(4) # 调用
foo = lambda x,y: (x+y) ** 2 # 定义函数
foo(1, 2)
# 等价于
def foo(x,y):
return (x+y) ** 2
- 使用lambda关键字定义匿名函数,格式为 lambda [参数列表]: 表达式
- 参数列表不需要小括号。无参就不写参数
- 冒号用来分割参数列表和表达式部分
- 不需要使用return。表达式的值,就是匿名函数的返回值。表达式中不能出现等号
- lambda表达式(匿名函数)只能写在一行上,也称为单行函数
匿名函数往往用在为高阶函数传参时,使用lambda表达式,往往能简化代码
# 返回常量的函数
print((lambda :0)())
print((lambda x:100)(1))
# 加法匿名函数,带缺省值
print((lambda x, y=3: x + y)(5))
print((lambda x, y=3: x + y)(5, 6))
# keyword-only参数
print((lambda x, *, y=30: x + y)(5))
print((lambda x, *, y=30: x + y)(5, y=10))
# 可变参数
print((lambda *args: (x for x in args))(*range(5)))
print((lambda *args: [x+1 for x in args])(*range(5)))
print((lambda *args: {x%2 for x in args})(*range(5)))
应用
# 需求,构建一个字典,所有key对应的值是一个列表,创建新的kv对的值也是空列表
d = {c:[] for c in 'abcde'}
d['a'].append(10) # {'a': [10], 'b': [], 'c': [], 'd': [], 'e': []}
d['f'] = [] # 新增key,value是空列表
d['f'].append(20)
# defaultdict
from collections import defaultdict
d = defaultdict(list) # lambda : list()
d['a'].append('10') # d['a'] = list()
d['f'].append('20')
# sorted
x = ['a', 1, 'b', 20, 'c', 32]
print(sorted(x, key=str))
# 如果按照数字排序怎么做?
x = ['a', 1, 'b', 20, 'c', 32]
print(sorted(x, key=lambda x: x if isinstance(x, int) else int(x, 16)))
可迭代对象
内建函数 | 函数签名 | 说明 |
---|---|---|
iter | iter(iterable) | 把一个可迭代对象包装成迭代器 |
next | next(iterable[, default]) | 取迭代器下一个元素,如果已经取完,继续取抛StopIteration异常 |
reversed | reversed(seq) | 返回一个翻转元素的迭代器 |
enumerate | enumerate | 迭代一个可迭代对象,返回一个迭代器,每一个元素都是数字和元素构成的二元组 |
可迭代对象
- 能够通过迭代一次次返回不同的元素的对象
- 所谓相同,不是指值是否相同,而是元素在容器中是否是同一个,例如列表中值可以重复的,['a', 'a'],虽然这个列表有2个元素,值一样,但是两个'a'是不同的元素
- 可以迭代,但是未必有序,未必可索引
- 可迭代对象有:list、tuple、string、bytes、bytearray、range、set、dict、生成器、迭代器等
- 可以使用成员操作符in、not in
- 对于线性数据结构,in本质上是在遍历对象,时间复杂度为O(n)
lst = [1, 3, 5, 7, 9]
it = iter(lst) # 返回一个迭代器对象
print(next(it))
print(next(it))
for i, x in enumerate(it, 2):
print(i, x)
#print(next(it)) # StopIteration
print()
for x in reversed(lst):
print(x)
# 比较下面的区别,说明原因?
it = iter(lst)
print(1 in it)
print(1 in it)
print(1 in lst)
print(1 in lst)
解析式
列表解析式
列表解析式List Comprehension,也叫列表推导式
#生成一个列表,元素0-9,将每个元素加1后的平方值组成新的列表
x = []
for i in range(10):
x.append((i+1)**2)
print(x
# 列表解析式
[(i+1)**2 for i in range(10)]
语法
- [返回值 for 元素 in 可迭代对象 if 条件]
- 使用中括号[],内部是for循环,if条件语句可选
- 返回一个新的列表
列表解析式是一种语法糖
- 编译器会优化,不会因为简写而影响效率,反而因优化提高了效率
- 减少程序员工作量,减少出错
- 简化了代码,增强了可读性
[expr for item in iterable if cond1 if cond2]
等价于
ret = []
for item in iterable:
if cond1:
if cond2:
ret.append(expr)
#
[expr for i in iterable1 for j in iterable2 ]
等价于
ret = []
for i in iterable1:
for j in iterable2:
ret.append(expr)
# 请问下面3种输出各是什么?为什么
[(i,j) for i in range(7) if i>4 for j in range(20,25) if j>23]
[(i,j) for i in range(7) for j in range(20,25) if i>4 if j>23]
[(i,j) for i in range(7) for j in range(20,25) if i>4 and j>23]
集合解析式
语法
- 列表解析式的中括号换成大括号{}就变成了集合解析式
- 立即返回一个集合
{(x, x+1) for x in range(10)}
{[x] for x in range(10)} # 可以吗?
字典解析式
语法
- 列表解析式的中括号换成大括号{},元素的构造使用key:value形式
- 立即返回一个字典
{x:(x,x+1) for x in range(10)}
{x:[x,x+1] for x in range(10)}
{(x,):[x,x+1] for x in range(10)}
{[x]:[x,x+1] for x in range(10)}
# {str(x):y for x in range(3) for y in range(4)} # 输出多少个元素?
迭代器
- 特殊的对象,一定是可迭代对象,具备可迭代对象的特征
- 通过iter方法把一个可迭代对象封装成迭代器
- 通过next方法,获取 迭代器对象的一个元素
- 生成器对象,就是迭代器对象。但是迭代器对象未必是生成器对象
内建函数
排序sorted
定义 sorted(iterable, *, key=None, reverse=False) ->list
sorted(lst, key=lambda x:6-x) # 返回新列表
list.sort(key=lambda x: 6-x) # 就地修改
sorted([1, '2', 3], key=lambda x: str(x))
sorted([1, '2', 3], key=str)
过滤filter
- 定义 filter(function, iterable)
- 对可迭代对象进行遍历,返回一个迭代器
- function参数是一个参数的函数,且返回值应当是bool类型,或其返回值等效布尔值。
- function参数如果是None,可迭代对象的每一个元素自身等效布尔值
list(filter(lambda x: x%3==0, [1,9,55,150,-3,78,28,123]))
list(filter(None, range(5)))
list(filter(None, range(-5, 5)))
映射map
- 定义 map(function, *iterables) -> map object
- 对多个可迭代对象的元素,按照指定的函数进行映射
- 返回一个迭代器
list(map(lambda x: 2*x+1, range(10)))
dict(map(lambda x: (x%5, x), range(500)))
dict(map(lambda x,y: (x,y), 'abcde', range(10)))
拉链函数zip
- zip(*iterables)
- 像拉链一样,把多个可迭代对象合并在一起,返回一个迭代器
- 将每次从不同对象中取到的元素合并成一个元组
list(zip(range(10),range(10)))
list(zip(range(10),range(10),range(5),range(10)))
dict(zip(range(10),range(10)))
生成器表达式
生成器表达式
语法
- (返回值 for 元素 in 可迭代对象 if 条件)
- 列表解析式的中括号换成小括号就行了
- 返回一个生成器对象
和列表解析式的区别
- 生成器表达式是按需计算(或称惰性求值、延迟计算),需要的时候才计算值
- 列表解析式是立即返回值
生成器对象
- 可迭代对象
- 迭代器
生成器表达式 | 列表解析式 |
---|---|
延迟计算 | 立即计算 |
返回可迭代对象迭代器,可以迭代 | 返回可迭代对象列表,不是迭代器 |
只能迭代一次 | 可反复迭代 |
生成器表达式和列表解析式对比
- 计算方式
- 生成器表达式延迟计算,列表解析式立即计算
- 内存占用
- 单从返回值本身来说,生成器表达式省内存,列表解析式返回新的列表
- 生成器没有数据,内存占用极少,使用的时候,一次返回一个数据,只会占用一个数据的空间
- 列表解析式构造新的列表需要为所有元素立即占用掉内存
- 计算速度
- 单看计算时间看,生成器表达式耗时非常短,列表解析式耗时长
- 但生成器本身并没有返回任何值,只返回了一个生成器对象
- 列表解析式构造并返回了一个新的列表
总结
- Python2 引入列表解析式
- Python2.4 引入生成器表达式
- Python3 引入集合、字典解析式,并迁移到了2.7
一般来说,应该多应用解析式,简短、高效。如果一个解析式非常复杂,难以读懂,要考虑拆解成for循环。
生成器和迭代器是不同的对象,但都是可迭代对象。
如果不需要立即获得所有可迭代对象的元素,在Python 3中,推荐使用惰性求值的迭代器。
内建函数 | 函数签名 | 说明 |
---|---|---|
sorted | sorted(iterable[, key][, reverse]) | 默认升序,对可迭代对象排序 |
# 排序一定是容器内全体参与
print(sorted([1,2,3,4,5]))
print(sorted(range(10, 20), reverse=True))
print(sorted({'a':100, 'b':'abc'}))
print(sorted({'a':100, 'b':'abc'}.items()))
print(sorted({'a':'ABC', 'b':'abc'}.values(), key=str, reverse=True))
print(sorted({'a':2000, 'b':'201'}.values(), key=str))
print(sorted({'a':2000, 'b':'201'}.values(), key=int))
练习
- 给出3个整数,使用if语句判断大小,并升序输出
def sorter(x,y,z):
if x>y: #x,y
if x>z: #(x,(z.y))
if y>z:
return x,y,z
else:
return z,z,y
else: # y,x
if y>z: #(y,(x,z))
if x>z:
return y,x,z
else:
return y,z,x
#或
def sorter(x,y,z):
return sorted((x,y,z),key=int,reverse=False)
- 有一个列表lst = [1,4,9,16,2,5,10,15],生成一个新列表,要求新列表元素是lst相邻2项的和
if __name__ == '__main__':
lst = [1,4,9,16,2,5,10,15]
print([lst[i]+lst[i+1] for i in range(len(lst)-1)])
- 随机生成100个产品ID,ID格式如下
- 顺序的数字6位,分隔符点号,10个随机小写英文字符
- 例如 000005.xcbaaduixy
import random
import string
if __name__ == '__main__':
alphabet=string.ascii_lowercase
for i in range(100):
print('{:0>6}.{}'.format(i,''.join(random.choices(alphabet,k=10))))
生成器函数
Python中有2种方式构造生成器对象:
- 生成器表达式
- 生成器函数
- 函数体代码中包含yield语句的函数
- 与普通函数调用不同,生成器函数调用返回的是生成器对象
m = (i for i in range(5))
print(type(m))
print(next(m))
print(next(m))
def inc():
for i in range(5):
yield i
print(type(inc))
print(type(inc())) # 生成器函数一定要调用,返回生成器对象
g = inc() # 返回新的生成器对象
print(next(g))
for x in g:
print(x)
print('-------------------')
for x in g: # 还能迭代出元素吗?
print(x)
普通函数调用,函数会立即执行直到执行完毕。
生成器函数调用,并不会立即执行函数体,而是返回一个生成器对象,需要使用next函数来驱动这个生
成器对象,或者使用循环来驱动。
生成器表达式和生成器函数都可以得到生成器对象,只不过生成器函数可以写更加复杂的逻辑。
执行过程
def gen():
print(1)
yield 2
print(3)
yield 4
print(5)
return 6
yield 7
print(next(gen())) # 看到什么
print(next(gen())) # 看到什么
g = gen()
print(next(g))
print(next(g))
print(next(g)) # return的值可以拿到吗?
print(next(g, 'End')) # 没有元素不想抛异常,给个缺省值
- 在生成器函数中,可以多次yield,每执行一次yield后会暂停执行,把yield表达式的值返回
- 再次执行会执行到下一个yield语句又会暂停执行
- 函数返回
- return语句依然可以终止函数运行,但return语句的返回值不能被获取到
- return会导致当前函数返回,无法继续执行,也无法继续获取下一个值,抛出StopIteration异常
- 如果函数没有显式的return语句,如果生成器函数执行到结尾(相当于执行了return None),一样会抛出StopIteration异常
生成器函数
- 包含yield语句的生成器函数调用后,生成生成器对象的时候,生成器函数的函数体不会立即执行
- next(generator) 会从函数的当前位置向后执行到之后碰到的第一个yield语句,会弹出值,并暂停函数执行
- 再次调用next函数,和上一条一样的处理过程
- 继续调用next函数,生成器函数如果结束执行了(显式或隐式调用了return语句),会抛出StopIteration异常
应用
无限循环
def counter():
i = 0
while True:
i += 1
yield i c = counter()
print(next(c)) # 打印什么
print(next(c)) # 打印什么
print(next(c)) # 打印什么
计数器
def inc():
def counter():
i = 0
while True:
i += 1
yield i
c = counter()
return next(c)
print(inc()) # 打印什么?
print(inc()) # 打印什么?
print(inc()) # 打印什么?为什么?如何修改
修改上例
def inc():
def counter():
i = 0
while True:
i += 1
yield i
c = counter()
def inner():
return next(c)
return inner # return lambda : next(c)
foo = inc()
print(foo()) # 打印什么?
print(foo()) # 打印什么?
print(foo()) # 打印什么?为什么?
代码中的inner函数可以由lambda表达式替代。
斐波那契数列
def fib():
a = 0
b = 1
while True:
yield b
a, b = b, a + b f = fib()
for i in range(1, 102):
print(i, next(f))
协程Coroutine
-
生成器的高级用法
-
它比进程、线程轻量级,是在用户空间调度函数的一种实现
-
Python3 asyncio就是协程实现,已经加入到标准库
-
Python3.5 使用async、await关键字直接原生支持协程
-
协程调度器实现思路
有2个生成器A、B
next(A)后,A执行到了yield语句暂停,然后去执行next(B),B执行到yield语句也暂停,然后再次调
用next(A),再调用next(B)在,周而复始,就实现了调度的效果
可以引入调度的策略来实现切换的方式
-
协程是一种非抢占式调度
yield from语法
从Python 3.3开始增加了yield from语法,使得 yield from iterable 等价于 for item in
iterable: yield item 。
yield from就是一种简化语法的语法糖。
def inc():
for x in range(1000):
yield x
# 使用yield from 简化
def inc():
yield from range(1000) # 注意这个函数出现了yield,也是生成器函数
foo = inc()
print(next(foo))
print(next(foo))
print(next(foo))
本质上yield from的意思就是,从from后面的可迭代对象中拿元素一个个yield出去。
练习
- 编写一个函数,能够实现内建函数map的功能
- 函数签名 def mymap(func, iterable, /)
def mymap(func,iterable,/):
for i in iterable:
yield func(i)
if __name__ == '__main__':
f=mymap(lambda x:x**2,range(5))
for i in f:
print(i)
总结
-------------------------------------------
个性签名:独学而无友,则孤陋而寡闻。做一个灵魂有趣的人!
如果觉得这篇文章对你有小小的帮助的话,记得在右下角点个“推荐”哦,博主在此感谢!
万水千山总是情,打赏一分行不行,所以如果你心情还比较高兴,也是可以扫码打赏博主,哈哈哈(っ•̀ω•́)っ✎⁾⁾!