首页 >> 大全

python生成器表达式(python 生成器和迭代器通俗理解)

2022-11-11 大全 129 作者:考证青年

先来学几个英文单词,本文统一使用英文单词表示以下概念:

「」:「可迭代对象」

「」:「迭代器」

「」:「生成器」

本文的重点是「」,之所以上来就说这3个概念,是因为:

只不过是和的一种简单形式。

用类的形式写/,要定义类,并包含至少三个函数:,和,但用只要定义一个函数就搞定了。

我在面试程序员的时候,连续几个号称资深的程序员,回答的问题都回答的不好。如果你能理解透彻这篇文章,对你的加薪作用可能不止一千。

虽然有标题党的嫌疑,但具有实在的意义。所以既然进来了,就耐心读下去吧。一周能理解透一个重要概念,假以时日,你就是高手。

如果你对和一无所知,建议先阅读本文的姊妹篇:

『终结者 #2』可迭代对象() vs 迭代器() vs 生成器 ()

我们先用「/」的方式来定一个「随机数生成器」,我把它命名为「」,它的功能是:

随机生成若干个1到100之间的随机数。

class ():

def (self, total):

self.count = 0

self.total = total

def (self):

self

def (self):

if self.count == self.total:

raise

= .(1, 100)

self.count += 1

使用上面的类生成88个随机数:

for i in (88):

print(i)

这是一个类,包含,和个函数:

(100)调用函数创建了一个可以生成100个随机数的对象。

for语句循环这个对象的时候首先调用函数获取,也就是这个对象本身。

然后for不停调用函数做循环,直到抛出异常。

整个过程有点小复杂,也难以理解。的出现就是为了简化这种复杂的写法。

实现同样的功能,只需要一个「函数」就够了:

def (total):

for _ in range(0, total):

yield .(1, 100)

调用过程不变:

for i in (88):

print(i)

for循环过程

结合上面的的例子,我们看一下for循环的过程:

调用函数(88):,并不会马上执行函数中的代码,而是返回一个对象。

for循环通过内置的next函数调用这个对象,直到对象抛出异常为止。

试验一下:

def (total):

for _ in range(0, total):

yield .(1, 100)

g = (88)

print(type(g))

执行上面这段代码,会打印出:

函数中没有语句,只有「yield」语句。所以生成器就是:「有yield关键词的函数」。

也可以有语句,语句就相当于抛出了异常,会结束函数。

使用next()函数执行中的代码,上面的for循环也是这个原理。

当代码执行到「yield」语句的时候,yield会返回一个值给调用者,然后函数暂定在原地,等待下次调用。

下次调用会从上次暂定的地方继续执行代码。这个过程会重复直到所有代码都执行完成,或者抛出了异常。

来看一个例子:

#

def ():

print('这是第一步,你好!!')

yield

print('这是第二步,你还好吗?')

yield

print('这是第三步,再见!')

s = ()

next(s)

next(s)

next(s)

运行一下,打印的结果如下:

---第一次调用

这是第一步,你好!!

---第二次调用

这是第二步,你还好吗?

---第三次调用

这是第三步,再见!

(most call last):

File "/Users//git////gen.py", line 47, in

next(s)

说明一下:

yield关键词会让函数暂停,也可以没有返回值

「可以把理解成有状态的函数」。一般的函数没有自己的状态,执行一次就结束了。但是有自己的状态可以被多次调用。

实际上背后就是一个类,所以它有状态。上一节中我们说过,就是/类的一种简单写法。

除了函数的写法之外,还可以用表达式的写法。它的写法和列表推导式类似,区别就是把中括号**[...]「改成小括号」(...)**。

这是一个列表推导式:

sys

# 生成1到1万的数字的平方

= [i * 2 for i in range(10000)]

这是表达式:

sys

# 生成1到1万的数字的平方

= (i * 2 for i in range(10000))

前者会在内存中生成10000个数字,放在列表中。

后者不会马上生成,当你每次用next()函数去调用它的时候,它会生成一个并返回。

如果你对推导式不熟悉,请看终结者系列的另一篇文章。链接见文末。

因为可以被循环,经常被拿来和list做对比。它最让人津津乐道的是它的性能优越性。

假如你开了一家汉堡店,有个大客户向你订购1000万个汉堡。你会一次性生产完这些汉堡吗?

傻的汉堡店主会这样:

一次性生产完1000万个汉堡,可是店里根本放不下啊。再租个仓库放。可是后来发现汉堡都坏掉了。

聪明的汉堡店主会这样:

分批生产!客户什么时候来要,就给他们马上生产一批,既不会把店占满,汉堡还新鲜!

那如果有个需求,让你生成100亿个随机数,再求和。你会这样写吗?

# 请不要尝试下面的代码,因为你的电脑可能会卡死!!!

= []

for i in range(1, ):

.(.(1, 100))

print(sum())

如果这样写,你的程序会在内存中生成100亿个整数,这也许会占满你的内存。

正确的写法是使用,就用我们上面的吧:

print(sum(()))

前面使用list的时候,要先在内存中生成100亿个数字,然后再求和,这占空间又费时间。

而用是每次用到的时候才生成1个,不用那么多空间。

我们可以测试一下前面的推导式的例子中占用的内存情况:

>>> sys

>>> = [i * 2 for i in range(10000)]

>>> sys.()

87624 # 列表推导式占用了87624字节的内存

>>> = (i ** 2 for i in range(10000))

>>> print(sys.())

120 # 只占用了87624字节的内存

这个例子中只生成10000个数字,区别还没那么大。如果是生成100亿个数字,区别会更大,因为占用的内存基本是恒定的,和数字多少无关。

如果你曾经在写代码的时候犯了「傻汉堡店主」的问题,那么不要羞愧,因为语言的设计者们都犯过这样的错误!

在 2中很多标准库使用列表形式,出现内存问题。所以在 3中很多标准库都改用了。

比如:

range()函数在 2中返回的是一个列表,在 3中返回的是一个。

字符串的迭代器也是一个

print(iter('终结者2'))

print(iter(range(1, 10000)))

打印结果:

我们再来多看几个代码例子,有的很简单,目的是为了给你增加更多的代码感觉。

range是一个,所以多大的range内存都不会爆for i in range(5):

print(i)

三次方生成器def (n):

for i in range(1, n, 2):

yield i**3

表达式形式的三次方生成器 = (i**3 for i in range(1,10,2))

不只是用在for循环中,我们可以手动用next()函数调用它def (n):

for i in range(1, n, 2):

yield i * (i + 1)

= (6)

next()

2

next()

12

next()

30

next()

> error

普通的执行到yield就暂停,可以返回一个值或者不返回。

除了可以返回值,它还可以接收调用者传值进来,这就要使用send()方法。

除了send()方法,还有,close()方法。

能通过yield返回值,也能够通过send()接收值,这就不是普通的,而是进入了协程的领域了,需要专门的文章来讲,「我们下次再终结」。

基本的有上面这些知识就足够了。

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了