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种方式构造生成器对象:

  1. 生成器表达式
  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)

总结

posted on 2022-01-10 10:42  无语至极  阅读(169)  评论(0编辑  收藏  举报

导航