python篇 生成器

本文介绍了Python中列表推导式的高效用法,包括生成整数列表、筛选偶数,以及嵌套循环实例。同时,对比了列表推导式和列表生成器的区别,强调了生成器在内存节省方面的优势。重点讲解了yield关键字的应用,展示了如何将函数转换为生成器,以及迭代器和可迭代对象的概念及其转换方法。
摘要由CSDN通过智能技术生成

1.列表(list)推导式

作用:快速生成一个需要的列表

语法:

[变量名 for 变量名 in 可迭代对象]

例1: 想要生成一个0~100列表

正常用法:ls = [0,1,2,3,4,5…100]

列表推导式用法:[x for x in range(101)]

>>> ls = [x for x in range(101)]
>>> ls
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100]
>>>

例2:打印0~100内所有偶数的列表

>>> ls = [x for x in range(101) if x%2==0]
>>> ls
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82,
 84, 86, 88, 90, 92, 94, 96, 98, 100]

当然循环也是可以嵌套的
例3:循环套循环

>>> ls=[i*j for i in range(10) for j in range(10)]
>>> ls
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 0, 4, 8, 12, 16, 20, 24, 28, 32
, 36, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 0, 6, 12, 18, 24, 30, 36, 42, 48, 54, 0, 7, 14, 21, 28, 35, 42, 49, 56, 63, 0, 8, 16, 24, 32, 40, 48, 56, 64, 72, 0, 9,
 18, 27, 36, 45, 54, 63, 72, 81]
>>> ls=[str(i)+"*"+str(j) for i in range(10) for j in range(10)]
>>> ls
['0*0', '0*1', '0*2', '0*3', '0*4', '0*5', '0*6', '0*7', '0*8', '0*9', '1*0', '1*1', '1*2', '1*3', '1*4', '1*5', '1*6', '1*7', '1*8', '1*9', '2*0', '2*1', '2*2', '
2*3', '2*4', '2*5', '2*6', '2*7', '2*8', '2*9', '3*0', '3*1', '3*2', '3*3', '3*4', '3*5', '3*6', '3*7', '3*8', '3*9', '4*0', '4*1', '4*2', '4*3', '4*4', '4*5', '4*
6', '4*7', '4*8', '4*9', '5*0', '5*1', '5*2', '5*3', '5*4', '5*5', '5*6', '5*7', '5*8', '5*9', '6*0', '6*1', '6*2', '6*3', '6*4', '6*5', '6*6', '6*7', '6*8', '6*9'
, '7*0', '7*1', '7*2', '7*3', '7*4', '7*5', '7*6', '7*7', '7*8', '7*9', '8*0', '8*1', '8*2', '8*3', '8*4', '8*5', '8*6', '8*7', '8*8', '8*9', '9*0', '9*1', '9*2',
'9*3', '9*4', '9*5', '9*6', '9*7', '9*8', '9*9']

列表生成器虽然好用但有一个缺点,当生成列表元素特别多的时候,空间浪费就会非常大,会引起大量元素占用空间,从而浪费空间,故,一定要慎用,可以用列表生成器来替代。

2.列表(list)生成器

列表生成器使用很简单,只要将推导式中的[ ]换成()就是列表生成器了,它所返回的却不是一个列表却是一个对象,准确来说是一个迭代器(下文会讲),它与列表推导式对比的优势在于它几乎不占用内存,而是用到的时候才生成一个数据,就像下面这个例子一样。

>>> (i for i in range(10))
<generator object <genexpr> at 0x00000050D0F8EBA0>

如果需要拿到列表生成器元素,需要借助next(对象)

但需要注意的是next()是不会回退的,如果遍历完了数据再遍历就会异常

>>> rs=(i for i in range(10))
>>> next(rs)
0
>>> next(rs)
1
>>> next(rs)
2
>>> next(rs)
3
>>> next(rs)
4
>>> next(rs)
5
>>> next(rs)
6
>>> next(rs)
7
>>> next(rs)
8
>>> next(rs)
9
>>> next(rs)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

理所应当的,迭代器是也是可以被for-in循环所迭代的,但迭代空后的对象中没有元素,刚刚我们已经用next()方法掏空了迭代器i中的所有元素,所以自然下面代码不会有任何输出

>>> for i in rs:
...     print(i)
...

当然也可以用列表生成器重新为其赋值,并迭代。


>>> rs=(i for i in range(10))
>>> for i in rs:
...     print(i)
...
0
1
2
3
4
5
6
7
8
9
>>>

小结:从上面描述可知,列表生成器是一个算法,通过next()计算值

感兴趣的读者可以改用dir查看列表生成器中的方法,可以了解一二。

>>> dir(rs)
['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subcl
ass__', '__iter__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__s
izeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']
>>>
__next__ 相当next()

之前说了数量多的时候不推荐使用列表推导式,会占用很大内存,而列表生成器就不会,所以推荐使用生成器,而函数也能转换为生成器,至于如何转换需要用到下文的yield关键字

例子:斐波拉契数列

ls = []
def fibonacci(num):
    first, second = 1, 1
    index = 0
    while index < num:
        ls.append(first)
        print(ls)
        first, second = second, first + second
        index += 1

if __name__ == '__main__':
    fibonacci(10)
    print(ls)
[1]
[1, 1]
[1, 1, 2]
[1, 1, 2, 3]
[1, 1, 2, 3, 5]
[1, 1, 2, 3, 5, 8]
[1, 1, 2, 3, 5, 8, 13]
[1, 1, 2, 3, 5, 8, 13, 21]
[1, 1, 2, 3, 5, 8, 13, 21, 34]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

3.yield关键字(自定义生成器)

数据一直存储在列表里,会造成大量的内存浪费,可以将函数转换为列表生成器,通过yield关键字,函数返回值就是一个生成器

yield作用:

具有return功能,能够返回函数的值
当一个函数出现yield关键字的时候,这狗函数在被调用的过程中,返回的是一个对象(生成器),需要借助next()方法进行迭代

用return对比

def test_yield(num):
    print("-------start-------")
    index = 0
    while index < num:
        print("--------1--------")
        return "哈哈哈哈"
        print("--------2--------")
        index += 1

if __name__ == '__main__':
    test_yield(10)

结果

-------start-------
--------1--------

下面看看yield

def test_yield(num):
    print("-------start-------")
    index = 0
    while index < num:
        print("--------1--------")
        yield "哈哈哈哈"
        print("--------2--------")
        index += 1

if __name__ == '__main__':
    test_yield(10)

结果


Process finished with exit code 0

具有yield关键字的函数被直接调用时,函数本身不会被调用,而是将函数转换为生成器,然后返回。

同样用变量接收对象并用next调用第一次

def test_yield(num):
    print("-------start-------")
    index = 0
    while index < num:
        print("--------1--------")
        yield "哈哈哈哈"
        print("--------2--------")
        index += 1

if __name__ == '__main__':
    result = test_yield(10)
    print(result)
    print(result.__next__())
    

结果

<generator object test_yield at 0x000000F36B39F430>
-------start-------
--------1--------
哈哈哈哈

下面看看第二次、第三次调用

def test_yield(num):
    print("-------start-------")
    index = 0
    while index < num:
        print("--------1--------")
        yield "哈哈哈哈"
        print("--------2--------")
        index += 1

if __name__ == '__main__':
    result = test_yield(10)
    print(result)
    print(result.__next__())
    print(result.__next__())
    print(result.__next__())

结果

<generator object test_yield at 0x00000012B638F430>
-------start-------
--------1--------
哈哈哈哈
--------2--------
--------1--------
哈哈哈哈
--------2--------
--------1--------
哈哈哈哈

至此,我们可知,yield不仅具有和return相似的返回功能,还可以记住,上一次函数运行的位置,执行的时候,从上一次结束的位置继续执行。

4.迭代器

迭代器是访问容器元素的一种方式,是可迭代对象的一种,至于问可迭代对象有哪些…这里为了方便理解,就不严谨的说:可以用for-in循环进行遍历东西都是可迭代对象。

当然python中也内置了判断是否是可迭代对象的方法。不过需要先引入一些模块。

>>> from collections.abc import Iterable

并配合全局函数isinstance()来判断是否是可迭代对象

>>> from collections.abc import Iterable

>>> dir(Iterable)
['__abstractmethods__', '__class__', '__class_getitem__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__h
ash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__
', '__sizeof__', '__slots__', '__str__', '__subclasshook__', '_abc_impl']
>>>ls = []
>>> from collections.abc import Iterable
>>> isinstance(ls,Iterable)
True

迭代器

同样为了方便理解,这里也不严谨的说:在python中如果能被全局函数next调用并且返回下一个值的对象,就是迭代器。没错就是先前列表生成器生成的玩意儿。

判断迭代器依然要借助isinstance方法和collection.abc模块,只是引入的不再是iterable而是iterator

>>> from collections.abc import Iterator
>>> isinstance(ls,Iterator)
False
>>> next(ls)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'dict' object is not an iterator
>>>

结论:凡是能够被for循环遍历的对象都是Iterable 可迭代对象,凡是能够被next返回下一个值的对象都是Iterator 迭代器。可迭代对象不一定是迭代器,迭代器一定是可迭代对象。

5.可迭代对象转换迭代器

使用iter函数

例子

>>> ls = [1,2,3,4,5,6]
>>> iter(ls)
<list_iterator object at 0x000000BDD1D3C550>
>>> ls_s = iter(ls)
>>> isinstance(ls,Iterable)
True
>>> isinstance(ls,Iterator)
False
>>> isinstance(ls_s,Iterator)
True
>>> isinstance(ls_s,Iterable)
True

>>> ls = [1,2,3,4,5,6]
>>> iter(ls)
<list_iterator object at 0x000000BDD1D3C550>
>>> ls_s = iter(ls)
>>> isinstance(ls,Iterable)
True
>>> isinstance(ls,Iterator)
False
>>> isinstance(ls_s,Iterator)
True
>>> isinstance(ls_s,Iterable)
True

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值