极简 Python:循环和迭代器 循环概述 一些容器 拆开容器 事物迭代器 你自己迭代器 展望未来 回顾

2025-06-08

非常简单的 Python:循环和迭代器

循环概述

一些容器

拆开容器

in

迭代器

你自己迭代器

期待

审查

喜欢这些文章吗?那就买本书吧! Jason C. McDonald 的《Dead Simple Python》现已由 No Starch Press 出版。


还记得上次丢失东西的情况吗?

你可能为了找它把家里翻了个底朝天。你一个房间一个房间地翻找,周围的人却总是问些毫无意义的问题,比如“你上次把它们放哪儿了?”(说真的,要是我知道的话,我肯定不会去找它们!)优化一下搜索方式当然很好,但你的房子并没有整理好……或者说,如果你和我一样,房子的布局不是很合理。你只能用线性搜索的方式。

在编程中,就像在现实生活中一样,我们通常不会以任何有意义的顺序收到数据。我们一开始会面对一堆乱七八糟的数据,然后需要对它们执行一些任务。搜索无序数据可能是你首先想到的例子,但你可能还想做数百件其他的事情:将所有华氏温度记录转换为摄氏度,求所有数据点的平均值,等等。

“是的,是的,这就是循环的用途!”

但这是 Python。这里的循环完全是另一个层次。它们好得简直是犯罪

循环概述

让我们把那些无聊的东西解决掉,好吗?

在 Python 中,与大多数语言一样,我们有两个基本循环:whilefor

while

循环while是非常基本的。

clue = None
while clue is None:
    clue = searchLocation()
Enter fullscreen mode Exit fullscreen mode

只要循环条件(clue is None在本例中为)计算结果为True,循环的代码就会被执行。

在 Python 中,我们还有几个有用的关键字:break立即停止循环,而continue跳到循环的下一次迭代。

最有用的方面之一break是,如果我们想运行相同的代码,直到用户提供有效输入。

while True:
    try:
        age = int(input("Enter your age: "))
    except ValueError:
        print(f"Please enter a valid integer!")
    else:
        if age > 0:
            break
Enter fullscreen mode Exit fullscreen mode

一旦遇到该break语句,我们就退出循环。诚然,这是一个相当复杂的例子,但它说明了这一点。你也经常while True:在游戏循环中看到它。

陷阱提醒:如果你曾经使用过任何语言的循环,那么你已经熟悉无限循环了。这通常是由于循环中while始终求值为条件True ,且没有语句而导致的。break

for

如果你之前用过 Java、C++ 或许多类似的 ALGOL 风格语言,你可能对三部分for循环很熟悉:for i := 1; i < 100; i := i + 1。我不知道你是怎么想的,但我第一次遇到它的时候,吓得魂飞魄散。现在我习惯了,但它就是不具备 Python 那种优雅简洁的特性,不是吗?

Python 的for循环看起来截然不同。与上述伪代码等效的 Python 代码是……

for i in range(1,100):
    print(i)
Enter fullscreen mode Exit fullscreen mode

range()是 Python 中一个特殊的“函数”,用于返回一个序列。(严格来说,它根本就不是一个函数,不过这么说有点儿太迂腐了。)

这是 Python 令人印象深刻的地方——它迭代一种特殊类型的序列,称为可迭代序列,我们稍后会讨论。

现在,最容易理解的是,我们可以迭代顺序数据结构,比如数组(在 Python 中称为“列表”)。

因此,我们可以这样做……

places = ['Nashville', 'Norway', 'Bonaire', 'Zimbabwe', 'Chicago', 'Czechoslovakia']
for place in places:
    print(place)

print("...and back!")
Enter fullscreen mode Exit fullscreen mode

...我们得到了这个...

Nashville
Norway
Bonaire
Zimbabwe
Chicago
Czechoslovakia
...and back!
Enter fullscreen mode Exit fullscreen mode

for...else

Python 的循环中还有一个独特的小技巧:else子句!循环完成后,如果遇到break语句,它将运行 中的代码else。但是,如果手动跳出 中的循环,它将else完全跳过 中的代码。

places = ['Nashville', 'Norway', 'Bonaire', 'Zimbabwe', 'Chicago', 'Czechoslovakia']
villain_at = 'Mali'

for place in places:
    if place == villain_at:
        print("Villain captured!")
        break
else:
    print("The villain got away again.")
Enter fullscreen mode Exit fullscreen mode

由于“马里”不在列表中,我们会看到消息“恶棍再次逃脱。”但是,如果我们将的值更改为villain_at,我们将看到“恶棍Norway被捕获!”

在哪里do

Python 没有do...while循环。如果你想找一个,典型的 Python 惯例是使用while True:带有内部break条件的循环,就像我们之前演示的那样。

一些容器

Python 有许多容器(或数据结构)来保存数据。我们不会深入探讨这些容器,但我想快速浏览一下最重要的几个:

list

Alist是一个可变序列(基本上是一个数组)。

它用方括号定义[ ],您可以通过索引访问其元素。

foo = [2, 4, 2, 3]

print(foo[1])
>>> 4

foo[1] = 42
print(foo)
>>> [2, 42, 2, 3]
Enter fullscreen mode Exit fullscreen mode

虽然没有严格的技术要求,但典型的惯例是列表仅包含同一类型(“同质”)的项目。

tuple

Atuple是一个不可变序列。一旦定义了它,从技术上讲就无法更改它(回想一下之前不可变性的含义)。这意味着在元组定义之后,你就无法添加或删除元素。

元组在括号内定义( ),可以通过索引访问其元素。

foo = (2, 4, 2, 3)

print(foo[1])
>>> 4

foo[1] = 42
>>> TypeError: 'tuple' object does not support item assignment
Enter fullscreen mode Exit fullscreen mode

与列表不同,标准约定允许元组包含不同类型的元素(“异构”)。

set

Aset是一个无序可变集合,保证不包含重复项。“无序”这一点需要牢记:单个元素的顺序无法保证!

集合的定义在花括号内{ },但如果你想要一个空集,则必须使用foo = set(),就像foo = {}创建一个 一样dict。由于它是无序的,因此无法通过索引访问其元素。

foo = {2, 4, 2, 3}

print(foo)
>>> {2, 3, 4}

print(foo[1])
>>> TypeError: 'set' object does not support indexing
Enter fullscreen mode Exit fullscreen mode

要将对象添加到集合中,它还必须是可哈希的。如果满足以下条件,则对象是可哈希的:

  1. 它定义了一个方法__hash__(),该方法返回一个整数形式的哈希值。(见下文)

  2. __eq__()它定义了比较两个对象的方法。

对于同一个对象(值),有效的哈希值应该始终相同,并且应该合理地保持唯一性,这样其他对象返回相同哈希值的情况就比较少见了。(两个或多个对象具有相同的哈希值称为哈希冲突,这种情况仍然会发生。)

字典 ( dict)

Adict是一个键值数据结构。

它定义在花括号内{ },用于分隔键和值。它是无序的,因此无法通过索引访问其元素;但是,可以用方括号以类似的方式:指示键。[ ]

foo = {'a' : 1, 'b' : 2, 'c' : 3, 'd' : 4}

print(foo['b'])
>>> 2

foo['b'] = 42
print(foo)
>>> {'a': 1, 'b': 42, 'c': 3, 'd': 4}
Enter fullscreen mode Exit fullscreen mode

只有可哈希对象才可以用作字典键。(set有关可哈希性的更多信息,请参阅上文部分。)

其他数据结构

collections除了基本容器之外,Python 还提供了其他容器。您可以在内置模块中找到它们

拆开容器

有一个重要的 Python 语法我们还没讲到,但很快就会用到。我们可以把容器里的每个元素赋值给一个变量!这叫做解包

当然,我们需要确切地知道我们要拆包多少件物品才能使其正常工作,否则我们会遇到ValueError异常。

让我们看一个使用元组的基本示例。

fullname = ('Carmen', 'Sandiego')
first, last = fullname
print(first)
>>> Carmen
print(last)
>>> Sandiego
Enter fullscreen mode Exit fullscreen mode

秘诀就在第二行。我们可以列出多个要赋值的变量,用逗号分隔。Python 会在等号右侧解压容器,并按从左到右的顺序将每个值赋给相应的变量。

陷阱警报:记住,set是无序的!虽然理论上你可以用集合来实现这一点,但你无法确定赋值给哪个变量的值。它不能保证任何顺序;集合通常按排序顺序解包其值,这只是偶然的,并不能保证!

in

Python 提供了一个漂亮的关键字,in用于检查是否在容器内找到特定元素。

places = ['Nashville', 'Norway', 'Bonaire', 'Zimbabwe', 'Chicago', 'Czechoslovakia']

if 'Nashville' in places:
    print("Music city!")
Enter fullscreen mode Exit fullscreen mode

这适用于许多容器,包括列表、元组、集合,甚至字典键(但不是字典值)。

如果您希望您的某个自定义类支持该in运算符,则只需要定义__contains__(self, item)返回True或 的方法False。(请参阅文档)。

迭代器

Python 的循环设计用于处理我之前提到的可迭代对象。这些对象可以使用迭代器进行迭代。

蟋蟀的声音。

好的,让我们从头开始。Python 容器对象(例如 a list)也是一个可迭代对象,因为它__iter__()定义了一个方法,该方法返回一个迭代器对象。

迭代定义为一个__next__()方法,对于容器迭代器,该方法返回下一个项。即使是无序容器(例如set()),也可以使用迭代器进行遍历。

当没有其他任何内容可以返回时__next__(),它会抛出一个称为 的特殊异常StopIteration。可以使用典型的 来捕获和处理该异常try...except

让我们再次看一下for遍历的循环list,例如......

dossiers = ['The Contessa', 'Double Trouble', 'Eartha Brute', 'Kneemoi', 'Patty Larceny', 'RoboCrook', 'Sarah Nade', 'Top Grunge', 'Vic the Slick', 'Wonder Rat']

for crook in dossiers:
    print(crook)
Enter fullscreen mode Exit fullscreen mode

dossiers是一个list对象,它是一个可迭代对象。当 Python 到达for循环时,它会做三件事:

  1. 调用iter(dossiers),进而执行dossiers.__iter__()。这将返回一个迭代器对象,我们将它称为list_iter。此迭代器对象将由循环使用。

  2. 对于循环的每次迭代,它都会调用next(list_iter),从而执行list_iter.__next__(),并将返回值分配给crook

  3. 如果迭代器抛出特殊异常StopIteration,则循环结束,我们退出。

如果我循环重写该逻辑,可能会更容易理解这一点while True:......

list_iter = iter(dossiers)
while True:
    try:
        crook = next(list_iter)
        print(crook)
    except StopIteration:
        break
Enter fullscreen mode Exit fullscreen mode

如果您尝试这两个循环,您会发现它们执行完全相同的操作!

了解了、和异常的工作原理__iter__()__next__()StopIteration现在可以使自己的类可迭代!

黑客警报:虽然将迭代器类与可迭代类分开定义很常见,但并非必须如此!只要两个方法都在类中定义,并且__next__()行为正常,就可以直接定义__iter__()return self

值得注意的是,迭代器本身是可迭代的:它们有一个__iter__()返回的方法self

《词典奇案》

假设我们有一本想要使用的字典......

locations = {
    'Parade Ground': None,
    'Ste.-Catherine Street': None,
    'Pont Victoria': None,
    'Underground City': None,
    'Mont Royal Park': None,
    'Fine Arts Museum': None,
    'Humor Hall of Fame': 'The Warrant',
    'Lachine Canal': 'The Loot',
    'Montreal Jazz Festival': None,
    'Olympic Stadium': None,
    'St. Lawrence River': 'The Crook',
    'Old Montréal': None,
    'McGill University': None,
    'Chalet Lookout': None,
    'Île Notre-Dame': None
    }
Enter fullscreen mode Exit fullscreen mode

如果我们只想查看其中的每个项目,那么使用for循环就足够了。这样应该可以了,对吧?

for location in locations:
    print(location)
Enter fullscreen mode Exit fullscreen mode

哎呀!这只显示了,没有显示值。这绝对不是我们想要的,对吧?这到底是怎么回事?

dict.__iter__()返回一个dict_keyiterator对象,该对象按照其类名所示执行操作:它迭代键,但不迭代值。

要获取和值,我们需要调用locations.items(),它返回dict_items对象。dict_items.iter()返回一个dict_itemiterator,它将以元组的形式返回字典中的每个键值对。

旧注意事项:如果您使用的是 Python 2,则应locations.iteritems()改为调用。

还记得之前我们讨论过解包吗?我们将每对元素作为一个元组来处理,这意味着我们可以将它们解包成两个变量。

for key, value in locations.items():
    print(f'{key} => {value}')
Enter fullscreen mode Exit fullscreen mode

打印出以下内容:

Parade Ground => None
Ste.-Catherine Street => None
Pont Victoria => None
Underground City => None
Mont Royal Park => None
Fine Arts Museum => None
Humor Hall of Fame => The Warrant
Lachine Canal => The Loot
Montreal Jazz Festival => None
Olympic Stadium => None
St. Lawrence River => The Crook
Old Montréal => None
McGill University => None
Chalet Lookout => None
Île Notre-Dame => None
Enter fullscreen mode Exit fullscreen mode

啊,差不多了!现在我们可以处理数据了。比如,我可能想把重要的信息记录到另一本词典里。

information = {}

for location, result in locations.items():
    if result is not None:
        information[result] = location

# Win the game!
print(information['The Loot'])
print(information['The Warrant'])
print(information['The Crook'])

print("Vic the Slick....in jaaaaaaaaail!")
Enter fullscreen mode Exit fullscreen mode

这将找到 Loot、Warrant 和 Crook,并按正确的顺序列出它们:

Lachine Canal
Humor Hall of Fame
St. Lawrence River
Vic the Slick....in jaaaaaaaaail!
Enter fullscreen mode Exit fullscreen mode

瞧,循环和迭代器的打击犯罪的力量!

你自己迭代器

我之前已经提到过,您可以创建自己的可迭代对象和迭代器,但展示比讲述更好!

假设我们想要保存一份特工列表,以便随时通过他们的特工编号识别他们。但是,有些特工我们无法提及。我们可以很容易地通过将特工 ID 和姓名存储在字典中,然后维护一个分类特工列表来实现这一点。

提示:记住,根据我们关于类的讨论,Python 中实际上并不存在私有变量。如果您真的想保守秘密,请使用行业标准的加密和安全措施,或者至少不要将您的 API 暴露给任何恶意人员。;)

首先,这是该类的基本结构:

class AgentRoster:
    def __init__(self):
        self._agents = {}
        self._classified = []

    def add_agent(self, name, number, classified=False):
        self._agents[number] = name
        if classified:
            self._classified.append(name)

    def validate_number(self, number):
        try:
            name = self._agents[number]
        except KeyError:
            return False
        else:
            return True

    def lookup_agent(self, number):
        try:
            name = self._agents[number]
        except KeyError:
            name = "<NO KNOWN AGENT>"
        else:
            if name in self._classified:
                name = "<CLASSIFIED>"
        return name
Enter fullscreen mode Exit fullscreen mode

我们可以继续进行测试,只是为了以后使用:

roster = AgentRoster()

roster.add_agent("Ann Tickwitee", 2539634)
roster.add_agent("Ivan Idea", 1324595)
roster.add_agent("Rock Solid", 1385723)
roster.add_agent("Chase Devineaux", 1495263, True)

print(roster.validate_number(2539634))
>>> True
print(roster.validate_number(9583253))
>>> False

print(roster.lookup_agent(1324595))
>>> Ivan Idea
print(roster.lookup_agent(9583253))
>>> <NO KNOWN AGENT>
print(roster.lookup_agent(1495263))
>>> <CLASSIFIED>
Enter fullscreen mode Exit fullscreen mode

太棒了,一切正如预期!现在,如果我们想要循环遍历整个字典,比如想在某个很棒的代码里,在漂亮的全球地图上显示他们的名字和当前位置,那该怎么办呢?

但是,我们不想roster._agents直接访问字典,因为那样会忽略这个类的“分类”属性。该如何处理呢?

正如我之前提到的,我们可以让这个类也充当它自己的迭代器,这意味着它有一个__next__()方法。在这种情况下,我们只需要返回self。但是,这已经是极简 Python 了,所以我们还是跳过这些烦人的简化操作,直接创建一个单独的迭代器类吧。

在这个例子中,我实际上会把这个字典转换成一个元组列表,这样我就可以使用索引了。(记住,字典是无序的。)我还会找出有多少代理__init__()分类。当然,所有这些逻辑都包含在方法中:

class AgentRoster_Iterator:

    def __init__(self, container):
        self._roster = list(container._agents.items())
        self._classified = container._classified
        self._max = len(self._roster) - len(self._classified)
        self._index = 0
Enter fullscreen mode Exit fullscreen mode

要成为迭代器,该类必须有一个__next__()方法;这是唯一的要求!记住,StopException一旦没有更多数据可返回,该方法就需要抛出。

我将定义AgentRoster_Iterator__next__()方法如下:

class AgentRoster_Iterator:

    # ...snip...

    def __next__(self):
        if self._index == self._max:
            raise StopIteration
        else:
            r = self._roster[self._index]
            self._index += 1
            return r
Enter fullscreen mode Exit fullscreen mode

现在我们回到AgentRoster类,我们需要添加一个__iter__()返回适当迭代器对象的方法。

class AgentRoster:

    # ...snip...

    def __iter__(self):
        return AgentRoster_Iterator(self)
Enter fullscreen mode Exit fullscreen mode

只需要一点点魔法,现在我们的AgentRoster类就能按照预期循环运行了!代码如下……

roster = AgentRoster()

roster.add_agent("Ann Tickwitee", 2539634)
roster.add_agent("Ivan Idea", 1324595)
roster.add_agent("Rock Solid", 1385723)
roster.add_agent("Chase Devineaux", 1495263, True)

for number, name in roster:
    print(f'{name}, id #{number}')
Enter fullscreen mode Exit fullscreen mode

...产生...

Ann Tickwitee, id #2539634
Ivan Idea, id #1324595
Rock Solid, id #1385723
Enter fullscreen mode Exit fullscreen mode

期待

我听到后面的 Pythonista 说:“等等,等等,我们还没完呢!你甚至还没有触及列表推导呢!”

Python 确实在循环和迭代器之上添加了一层额外的魔法,它使用了一种名为生成器的特殊工具。这种类型的类提供了另一个令人难以置信的工具,称为推导式,它就像一个用于创建数据结构的紧凑循环。

我还特意省略了诸如zip()和 之类的优点enumerate(),它们使循环和迭代更加强大。我本来想在这里包含它们,但我不想让文章太长。(这已经够长了。)我稍后也会谈到这些。

我看到你们中的一些人已经兴奋不已,但遗憾的是,你们必须等到下一篇文章才能了解更多信息。

审查

让我们回顾一下本节中最重要的概念:

  • while只要循环条件为 ,循环就会运行True
  • 您可以使用关键字跳出循环break,或者使用关键字跳到下一次迭代continue
  • 循环for对可迭代对象(可以迭代的对象)进行迭代,例如列表。
  • range()函数返回一个可迭代的数字序列,可以在for循环中使用,例如for i in range(1, 100)
  • Python 没有do...while循环。请使用while True:带有明确 break 语句的循环。
  • Python 有四种基本数据结构或容器
    • 列表是可变的、有序的、连续的结构……基本上就是数组。
    • 元组是不可变的、有序的、序列化的结构。它类似于列表,但你无法修改其内容。
    • 集合是可变的、无序的结构,保证不会包含任何重复元素。它们只能存储可哈希对象。
    • 字典是可变的、无序的结构,用于存储键值对。您可以按键查找项,而不是通过索引。只有可哈希对象可以用作键。
  • 您可以使用约定将容器的值解包a, b, c = someContainer为多个变量。左侧的变量数量和右侧容器中的元素数量必须相同!
  • 你可以使用关键字快速检查元素是否位于容器中in。如果你希望你的类支持此功能,请定义该contains()方法。
  • Python 的容器就是可迭代对象的例子:它们返回可以遍历其内容的迭代器。可迭代对象总是通过其iter()方法返回一个迭代器对象。
  • 迭代对象总是有一个next()返回值的方法。容器迭代器的next()方法将返回容器中的下一个元素。当没有更多可返回的内容时,迭代器将引发StopIteration异常。

Ned Batchelder 有一场关于迭代器和循环的精彩演讲,题为“像本地人一样循环”。强烈推荐大家去听一听!

另外,像往常一样,请务必阅读文档。循环、容器和迭代器还有很多其他用途。


感谢deniska、、grymikanobori(Freenode IRC #python)提出的修改建议。

鏂囩珷鏉ユ簮锛�https://dev.to/codemouse92/dead-simple-python-loops-and-iterators-15bp
PREV
Introducing #devjournal What Belongs Here? What Doesn't Belong Here Discussions Not To Be Confused With... ...And, That's It!
NEXT
极简 Python:数据类型与不变性 一个迂腐的观点 数据类型在哪儿?!? 不变的真相 警告:匈牙利命名法 演员招募 被……挂起 字符串函数回顾