18.Python基础篇-迭代器、生成器、推导式

函数进阶-迭代器

 双下方法:

很少直接调用,一般情况下,都是通过其他语法触发的(Python解释器调用的方法)

可迭代协议 与 迭代器协议

可迭代的iterable与迭代器iter

可迭代协议:含有__iter__方法的都是可迭代的。

  可迭代的,一定可以被for循环。只要含有__iter__()方法能被for循环。

迭代器协议:含有__iter__方法和__next__方法的。

  迭代器一定可迭代。

可迭代的通过iter()方法就能得到一个迭代器。

迭代器的特点:

1.惰性计算:迭代器只在需要时生成数据,适合处理大数据集,减少内存占用。

2.随着循环每次在内存中生成一个值,而不是全部。每次调用next时返回一个值,也不是全部。

list1 = [1, 2, 3]

# 只有__iter__方法,没有__next__方法,说明列表是可迭代的
print('__iter__' in list1.__dir__())
print('__next__' in list1.__dir__())
# 输出结果
# True
# False


# 通过iter()方法将列表转换为迭代器。并验证是否存在__iter__方法与__next__方法。
list1_iterator = iter(list1)  # 调用iter方法返回当前可迭代对象的迭代器
print('__iter__' in list1_iterator.__dir__())
print('__next__' in list1_iterator.__dir__())
# 输出结果:
# True
# True

# 一个类中只要有iter方法就会认为是可迭代的,如果既有iter方法还有next方法,就会认为是迭代器
from collections import Iterator
from collections import Iterable
class A:
    def __iter__(self):
        pass
    def __next__(self):
        pass
a = A()
print(isinstance(a, Iterator))  # 判断是否是迭代器
print(isinstance(a, Iterable))  # 判断是否是可迭代的
# 输出结果:
# True
# True

# 迭代器中的__next__()方法
# __next__():以0为起点,获取迭代器中的下一个值
print(list1_iterator.__next__())  # list1_iterator中的第一个值
print(list1_iterator.__next__())  # list1_iterator中的第二个值
# 输出结果
# 1
# 2

# 迭代器中的__iter__()方法
iterator = list1.__iter__()  # 返回一个当前可迭代对象的迭代器
print(iterator)
# 输出结果:
# <list_iterator object at 0x00000271EBB2B970>

# 迭代器能节省内存的演示
print(range(1, 100000000000))  # 很快生成。因为range是一个迭代器,一个一个的返值
print(list(range(1, 100000000000)))  # 会报错MemoryError,电脑内存不够容纳这么大的列表。因为list不是迭代器,会一次生成全部数据。

函数进阶-生成器

生成器的本质,就是自己写的迭代器。一次返回一个值,而不是一次性生成所有的值。生成器通过使用 yield 关键字定义。(有yield的函数就是生成器函数)

生成器的两种形式:1.生成器函数。2,生成器表达式

生成器函数

生成器函数的定义:含有yield关键字的函数就是生成器函数;

生成器函数的特点:

1.调用函数时不会执行函数内部代码,而是返回一个生成器内存地址。

2.每次调用next()方法的时候会取到一个值。

3.直到取完最后一个,再执行next时会报错。

yield关键词

yield与return不能共用。与 return 不同,yield 不会终止函数的执行,而是暂停函数的执行,并返回一个值给调用者。函数在下次调用时会从上次暂停的地方继续执行。

def simple_generator():
    yield 1
    yield 2
    yield 3

gen = simple_generator()
print(next(gen))  # 输出: 1
print(next(gen))  # 输出: 2
print(next(gen))  # 输出: 3
# print(next(gen))  # 抛出 StopIteration 异常

 

生成器函数代码演示:

def generator():
    print('生成器函数中打印的数据1')
    yield 1
    print('生成器函数中打印的数据2')
    yield 2
    print('生成器函数中打印的数据3')
    yield 3

g = generator()  # 返回一个生成器
print(g)  # 结果:<generator object generator at 0x103141540>
print(g.__next__())  # 通过next取值

 

生成器函数取值的三种方式:

1.通过__next__()取值:上面的演示代码中已经演示

2.通过for循环取值:

def generator():
    print('生成器函数中打印的数据1')
    yield 1
    print('生成器函数中打印的数据2')
    yield 2
    print('生成器函数中打印的数据3')
    yield 3

g = generator()  # 返回一个生成器

for i in g:
    print(i)

执行结果:
生成器函数中打印的数据1
1
生成器函数中打印的数据2
2
生成器函数中打印的数据3
3

 

3.通过数据类型转换从生成器中取值(比较占用内存):

def generator():
    print('生成器函数中打印的数据1')
    yield 1
    print('生成器函数中打印的数据2')
    yield 2
    print('生成器函数中打印的数据3')
    yield 3


g = generator()
print(list(g))

# 执行结果:
生成器函数中打印的数据1
生成器函数中打印的数据2
生成器函数中打印的数据3
[1, 2, 3]

 

生成器函数与普通函数的区别:

1. 定义方式

  • 普通函数:使用 return 关键字返回一个值,函数执行完毕后结束。
  • 生成器函数:使用 yield 关键字逐个返回值,函数执行可暂停并保持状态。

2. 返回值

  • 普通函数:返回单一值,并终止函数执行。
  • 生成器函数:返回一个生成器对象,可以多次调用,逐个生成多个值。

3. 内存管理

  • 普通函数:一次性生成所有返回值,可能导致高内存占用。
  • 生成器函数:按需生成值,内存占用小,适合处理大数据。

4. 调用方式

  • 普通函数:调用后不可再用,必须重新调用。
  • 生成器函数:可以通过 next() 逐步获取值,也可以在 for 循环中迭代。

5. 状态保持

  • 普通函数:无法保持执行状态,调用后状态丢失。
  • 生成器函数:保持状态,每次调用继续上次暂停的位置。

总结

  • 生成器函数通过 yield 提供了惰性求值的能力,适合需要高效内存管理和逐步生成数据的场景。

生成器函数进阶

send()

send():用于恢复生成器的执行,同时向生成器传递一个值。生成器在遇到 yield 时暂停执行,通过 send() 可以将一个值发送给生成器中断点处的 yield 表达式,并继续执行生成器直到遇到下一个 yield

def generator():
    print('A')
    parameter = yield 1
    print(parameter)
    print('B')
    yield 2


g = generator()

print(g.send(None))  # 执行printA,打印返回值1
print(g.send("传递的参数aaaa"))  # 执行print(parameter),执行print(B),打印返回值1

send与next的区别:send在获取下一个值的效果和next基本一致。只是send在获取下一个值的时候,给上一个yield的位置传递一个参数。

send使用注意事项:

  第一次调用send时必须send(None)

  最后一个yield不能接受外部的值

yield from语法

# yield from说明
def generator():
    s1 = 'abcdefg'
    s2 = '1234567'
    for i in s1:
        yield i
    for i in s2:
        yield i

g = generator()
for i in g:
    print(i)
# 上面这个功能使用yield from实现。实现效果一样,可以节省部分代码量。
def generator():
    s1 = 'abcdefg'
    s2 = '1234567'
    yield from s1 + s2

g = generator()
for i in g:
    print(i)

 

预激活生成器的装饰器

def init(func):
    def inner(*args, **kwargs):
        g = func()
        g.__next__()
        return g
    return inner

@init
def avg_g():
    sum = 0
    count = 0
    avg = 0
    while True:
        num = yield avg
        sum += num
        count += 1
        avg = sum / count

g = avg_g()
print(g.send(10))
print(g.send(20))

 

推导式和表达式

列表推导式

语法演示:

# 列表推导式
list1 = [i for i in range(10)]  # 将range(10)里面的每个结果,存到列表中的每个元素中
print(list1)  # 输出结果:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# 了解即可
names = [['Tom','Billy','Jefferson', 'Steven','Joe','Andrew','Wesley'],
 ['Alice','Jill','Ana','Wendy','Jennifer','Sherry','Eva']]
list1 = [name for lst in names for name in lst if name.count('e')==2]  # 列表推导式中可以嵌套for循环可以写到一行
print(list1)

 

生成器表达式

# 生成器表达式
g = (i for i in range(10))
print(g)  # <generator object <genexpr> at 0x00000249D1AB8CF0>
for i in g:
    print(i)
# 表达式增加if判断,这个操作也同样适用于列表推导式
g = (print(i) for i in range(10) if i%2==1)  # if条件满足,才执行print(i)
print(g)  # <generator object <genexpr> at 0x00000249D1AB8CF0>
for i in g:
    pass

 

列表推导式与生成器表达式的区别:

1.括号不一样
2.返回的值不一样。一个是完整的列表,生成器表达式返回一个生成器
3.生成器表达式比列表推导式更节省内存。

字典推导式

# 案例:将key和value调换{10: 'a', 20: 'b'}这样
mase = {'a': 10, 'b': 20}
mase = {mase[key]: key for key in mase}
print(mase)

 

集合推导式

# 集合推导式,自带结果去重功能
squared = {i for i in [1, 1, 2]}
print(squared)  # 输出结果:{1, 2}

 

posted @ 2024-10-18 21:57  邵杠杠  阅读(33)  评论(0编辑  收藏  举报