Python基础学习之二迭代器

Python 的 for 循环不会像其他语言中的 for 循环那样工作。在这篇文章中,我们将深入探讨 Python 的 for 循环来看看它们在底层如何工作,以及为什么它们会按照它们的方式工作。

循环的问题

我们将通过看一些“陷阱”开始我们的旅程,在我们了解循环如何在 Python 中工作之后,我们将再次看看这些问题并解释发生了什么。

问题 1:循环两次

假设我们有一个数字列表和一个生成器,生成器会返回这些数字的平方:

>>> numbers =[1,2,3,5,7]

>>> squares =(n**2 for n in numbers)

我们可以将生成器对象传递给 tuple 构造器,从而使其变为一个元组:

>>> tuple(squares)

(1,4,9,25,49)

如果我们使用相同的生成器对象并将其传给 sum 函数,我们可能会期望得到这些数的和,即 88。

>>> sum(squares)

0

但是我们得到了 0。

问题 2:包含的检查

让我们使用相同的数字列表和相同的生成器对象:

>>> numbers =[1,2,3,5,7]


>>> squares =(n**2 for n in numbers)

如果我们询问9是否在squares生成器中,Python将会告诉我们9在squares中。但是如果我们再次询问相同的问题,Python会告诉我们9不在squares中。

>>> 9 in squares

True

>>> 9 in squares

False

我们询问相同的问题两次,Python 给了两个不同的答案。

问题 3 :拆包

这个字典有两个键值对:

>>> counts = {'apples': 2,'oranges': 1}

让我们使用多个变量来对这个字典进行拆包:

>>> x,y = counts

你可能会期望当我们对这个字典进行拆包时,我们会得到键值对或者得到一个错误。

但是解包字典不会引发错误,也不会返回键值对。当你解包一个字典时,你会得到键:

>>> x

'apples'

回顾:Python for 循环

在我们了解一些关于这些 Python 片段的逻辑之后,我们将回到这些问题。

Python 没有传统的 for 循环。为了解释我的意思,让我们看一看另一种编程语言的 for 循环。

这是一种传统C风格的for循环,用JavaScript编写:

let numbers =[1,2,3,5,7];

for(let i = 0;i < numbers.length;i += 1){

print(numbers[i])

}

JavaScript、C、C++、Java、PHP和一大堆其他编程语言都有这种风格的for循环,但是Python确实没有。

Python 确实没有 传统 C 风格的 for 循环。在 Python 中确实有一些我们称之为 for 循环的东西,但是它的工作方式类似于foreach循环。

这是Python的for循环的风格:

numbers =[1,2,3,5,7]

for n in numbers:

print(n)

与传统 C 风格的 for 循环不同,Python 的 for 循环没有索引变量,没有索引变量初始化,边界检查,或者索引递增。Python 的 for 循环完成了对我们的 numbers 列表进行遍历的所有工作。

因此,当我们在 Python 中确实有 for 循环时,我们没有传统 C 风格的 for 循环。我们称之为 for 循环的东西的工作机制与之相比有很大的不同。

定义:可迭代和序列

既然我们已经解决了 Python 世界中无索引的 for 循环,那么让我们在此之外来看一些定义。

可迭代是任何你可以用 Python 中的 for 循环遍历的东西。可迭代意味着可以遍历,任何可以遍历的东西都是可迭代的。

for item in some_iterable:

print(item)

序列是一种非常常见的可迭代类型,列表,元组和字符串都是序列。

>>> numbers =[1,2,3,5,7]

>>> coordinates =(4,5,7)

>>> words = "hello there"

序列是可迭代的,它有一些特定的特征集。它们可以从 0 开始索引,以小于序列的长度结束,它们有一个长度并且它们可以被切分。列表,元组,字符串和其他所有序列都是这样工作的。

>>> numbers[0]

1

>>> coordinates[2]

7

>>> words[4]

'o'

Python 中很多东西都是可迭代的,但不是所有可迭代的东西都是序列。集合、字典、文件和生成器都是可迭代的,但是它们都不是序列。

>>> my_set = {1,2,3}

>>> my_dict = {'k1': 'v1','k2': 'v2'}

>>> my_file = open('some_file.txt')

>>> squares =(n**2 for n in my_set)

因此,任何可以用 for 循环遍历的东西都是可迭代的,序列只是一种可迭代的类型,但是 Python 也有许多其他种类的迭代器。

Python for 循环不使用索引

你可能认为,Python 的 for 循环在底层使用了索引进行循环。在这里我们使用 while 循环和索引手动遍历:

numbers =[1,2,3,5,7]

i = 0

while i < len(numbers):

print(numbers[i])

    i += 1

这适用于列表,但它不会对所有东西都起作用。这种循环方式只适用于序列。

如果我们尝试用索引去手动遍历一个集合,我们会得到一个错误:

>>> fruits = {'lemon','apple','orange','watermelon'}

>>> i = 0

>>> while i < len(fruits):

...     print(fruits[i])

...     i += 1

...

Traceback(most recent call last):

File "",line 2,in

TypeError: 'set' object does not support indexing

集合不是序列,所以它们不支持索引。

我们不能使用索引手动对 Python 中的每一个迭代对象进行遍历。对于那些不是序列的迭代器来说,这是行不通的。

迭代器驱动 for 循环

因此,我们已经看到,Python 的 for 循环在底层不使用索引。相反,Python 的 for 循环使用迭代器。

迭代器就是可以驱动可迭代对象的东西。你可以从任何可迭代对象中获得迭代器,你也可以使用迭代器来手动对它的迭代进行遍历。

让我们来看看它是如何工作的。

这里有三个可迭代对象:一个集合,一个元组和一个字符串。

>>> numbers = {1,2,3,5,7}

>>> coordinates =(4,5,7)

>>> words = "hello there"

我们可以使用 Python 的内置 iter 函数来访问这些迭代器,将一个迭代器传递给 iter 函数总会给我们返回一个迭代器,无论我们正在使用哪种类型的迭代器。

>>> iter(numbers)

>>> iter(coordinates)

>>> iter(words)

一旦我们有了迭代器,我们可以做的事情就是通过将它传递给内置的 next 函数来获取它的下一项。

>>> numbers =[1,2,3]

>>> my_iterator = iter(numbers)

>>> next(my_iterator)

1

>>> next(my_iterator)

2

迭代器是有状态的,这意味着一旦你从它们中消耗了一项,它就消失了。

如果你从迭代器中请求 next 项,但是其中没有更多的项了,你将得到一个 StopIteration 异常:

>>> next(my_iterator)

3

>>> next(my_iterator)

Traceback(most recent call last):

File "",line 1,in

StopIteration

所以你可以从每个迭代中获得一个迭代器,迭代器唯一能做的事情就是用 next 函数请求它们的下一项。如果你将它们传递给 next,但它们没有下一项了,那么就会引发 StopIteration 异常。

你可以将迭代器想象成 Pez 分配器(LCTT 译注:Pez 是一个结合玩具的独特复合式糖果),不能重新分配。你可以把 Pez 拿出去,但是一旦 Pez 被移走,它就不能被放回去,一旦分配器空了,它就没用了。

没有 for 的循环

既然我们已经了解了迭代器和 iter 以及 next 函数,我们将尝试在不使用 for 循环的情况下手动遍历迭代器。

我们将通过尝试将这个 for 循环变为 while 循环:

def funky_for_loop(iterable,action_to_do):

    for item in iterable:

action_to_do(item)

为了做到这点,我们需要:

1.从给定的可迭代对象中获得迭代器

2.反复从迭代器中获得下一项

3.如果我们成功获得下一项,就执行 for 循环的主体

4.如果我们在获得下一项时得到了一个 StopIteration 异常,那么就停止循环

def funky_for_loop(iterable,action_to_do):

iterator = iter(iterable)

    done_looping = False

    while not done_looping:

        try:

item = next(iterator)

        except StopIteration:

            done_looping = True

        else:

action_to_do(item)

我们只是通过使用 while 循环和迭代器重新定义了 for 循环。

上面的代码基本上定义了 Python 在底层循环的工作方式。如果你理解内置的 iter 和 next函数的遍历循环的工作方式,那么你就会理解 Python 的 for 循环是如何工作的。

事实上,你不仅仅会理解 for 循环在 Python 中是如何工作的,所有形式的遍历一个可迭代对象都是这样工作的。

迭代器协议iterator protocol 是一种很好表示 “在 Python 中遍历迭代器是如何工作的”的方式。它本质上是对 iter 和 next 函数在 Python 中是如何工作的定义。Python 中所有形式的迭代都是由迭代器协议驱动的。

迭代器协议被 for 循环使用(正如我们已经看到的那样):

for n in numbers:

print(n)

多重赋值也使用迭代器协议:

x,y,z = coordinates

星型表达式也是用迭代器协议:

a,b,*rest = numbers

print(*numbers)

许多内置函数依赖于迭代器协议:

unique_numbers = set(numbers)

在 Python 中任何与迭代器一起工作的东西都可能以某种方式使用迭代器协议。每当你在 Python 中遍历一个可迭代对象时,你将依赖于迭代器协议。

生成器是迭代器

所以你可能会想:迭代器看起来很酷,但它们看起来像一个实现细节,我们作为 Python 的使用者,可能不需要关心它们。

我有消息告诉你:在 Python 中直接使用迭代器是很常见的。

这里的 squares 对象是一个生成器:

>>> numbers =[1,2,3]

>>> squares =(n**2 for n in numbers)

生成器是迭代器,这意味着你可以在生成器上调用 next 来获得它的下一项:

>>> next(squares)

1

>>> next(squares)

4

但是如果你以前用过生成器,你可能也知道可以循环遍历生成器:

>>> squares =(n**2 for n in numbers)

>>> for n in squares:

...     print(n)

...

1

4

9

如果你可以在 Python 中循环遍历某些东西,那么它就是可迭代的。

所以生成器是迭代器,但是生成器也是可迭代的,这又是怎么回事呢?

我欺骗了你

所以在我之前解释迭代器如何工作时,我跳过了它们的某些重要的细节。

生成器是可迭代的

我再说一遍:Python 中的每一个迭代器都是可迭代的,意味着你可以循环遍历迭代器。

因为迭代器也是可迭代的,所以你可以使用内置 next 函数从可迭代对象中获得迭代器:

>>> numbers =[1,2,3]

>>> iterator1 = iter(numbers)

>>> iterator2 = iter(iterator1)

请记住,当我们在可迭代对象上调用 iter 时,它会给我们返回一个迭代器。

当我们在迭代器上调用 iter 时,它会给我们返回它自己:

>>> iterator1 is iterator2

True

迭代器是可迭代的,所有的迭代器都是它们自己的迭代器。

def is_iterator(iterable):

return iter(iterable)is iterable

迷惑了吗?

让我们回顾一些这些措辞。

一个可迭代对象是你可以迭代的东西

一个迭代对象器是一种实际上遍历可迭代对象的代理

此外,在 Python 中迭代器也是可迭代的,它们充当它们自己的迭代器。

所以迭代器是可迭代的,但是它们没有一些可迭代对象拥有的各种特性。

迭代器没有长度,它们不能被索引:

>>> numbers =[1,2,3,5,7]

>>> iterator = iter(numbers)

>>> len(iterator)

TypeError: object of type 'list_iterator' has no len()

>>> iterator[0]

TypeError: 'list_iterator' object is not subscriptable

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,869评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,716评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,223评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,047评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,089评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,839评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,516评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,410评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,920评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,052评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,179评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,868评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,522评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,070评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,186评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,487评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,162评论 2 356

推荐阅读更多精彩内容