tuple(squares)
(1, 4, 9, 25, 49)
现在,又向计算这个生成器对象squares
里面所有数字的和,观察一下,应该能看出来,其和是88
,然而:
sum(squares)
0
这里计算结果为0
,是Python的BUG吗?
2、检查无效
再用下面的方法得到那个生成器对象:
numbers = [1, 2, 3, 5, 7]
squares = (n**2 for n in numbers)
如果检查9
是否在squares
生成器中,显然这是真的True
。但是同样的检查如果再做一遍,就不是这个结果了——不可重复,不科学?
9 in squares
True
9 in squares
False
3、解包
创建一个包含两个键值对的字典:
counts = {‘apples’: 2, ‘oranges’: 1}
用多变量的赋值语句对字典解包:
x, y = counts
先猜一下,这样做会有什么结果?报错,还是两个变量分别引用了两个键值对——引用键值对,兼职不可能吧,除非将键值对包裹在字典里。
但是,一没有报错,二没有返回键值对,而是:
x
‘apples’
这似乎也合乎情理和逻辑。
复习for循环
温故而知新,先来回顾一下for
循环。
严格地说,Python中的for
循环并不“传统”,或者说不符合众多语言中所继承的C语言风格的for
循环。
先看一看所谓的C语言风格的for
循环,以JavaScript为例:
var numbers = [1, 2, 3, 5, 7];
for (var i = 0; i < numbers.length; i += 1) {
print(numbers[i]) }
像人们熟知的JavaScript, C, C++, Java, PHP等很多编程语言的for
循环,都是这个样子的,所以,不少人认为这样的才是真正的for
循环。
但是,Python盲从,而是特立独行地创造了自己的for
循环。它不是C语言风格的,而是Python风格的:
numbers = [1, 2, 3, 5, 7]
for n in numbers:
print(n)
与传统的C语言风格的for
循环不同,Python的for
循环不需要创建索引,不需要对索引变量进行初始化,不需要进行边界检查,也不需要让索引递增。Python的for
循环为我们完成了在numbers
列表上循环的所有工作。
因此,Python中虽有for
循环,但并非传统的C风格,那么其工作原理亦与之不同。
可迭代对象和序列
在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的for循环
前面已经显示了,Python的for
循环不使用索引——这是不同于C语言分割的for
循环之处。
不过,你可能会悄悄滴认为,如果非要用,Python的for
循环肯定也能实现C语言风格,因为我们一向认为“C语言是任何东西的基础”。为此,我们使用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中,我们_不能_通过使用索引手动遍历每个可迭代对象。这对于不是序列的可迭代对象根本不起作用。
迭代器
在Python中,迭代器可以用于for
循环。
什么是迭代器?它是驱动可迭代对象的一类对象。我们可以从任意可迭代对象那里生成迭代器。
这里有三个可迭代对象:集合、元组和字符串。
numbers = {1, 2, 3, 5, 7}
coordinates = (4, 5, 7)
words = “hello there”
可以用Python内置的iter
函数用上面的可迭代对象生成迭代器。
iter(numbers)
<set_iterator object at 0x7f2b9271c860>
iter(coordinates)
<tuple_iterator object at 0x7f2b9271ce80>
iter(words)
<str_iterator object at 0x7f2b9271c860>
有了迭代器,就把它传给内置函数next
,从而获得它的下一项。
numbers = [1, 2, 3]
my_iterator = iter(numbers)
next(my_iterator)
1
next(my_iterator)
2
每从迭代器中取出一项,那一项就从迭代器中“消失”了。如果到了迭代器的最后一项,还执行next
,而实际上后面已经没有其他项了,这时候就会报出StopIteration
异常。
next(iterator)
3
next(iterator)
Traceback (most recent call last): File “”, line 1,
in StopIteration
不用for的循环
在了解了迭代器、以及iter
和next
函数后,我们将尝试手动遍历一个可迭代对象,而不使用for
循环。
不用for
,就得用while
了,Python中只有这么两个循环语句。
def funky_for_loop(iterable, action_to_do):
for item in iterable:
action_to_do(item)
为了去掉for
,需要:
-
根据给定的可迭代对象生成迭代器
-
从迭代器中重复获取下一项
-
如果成功获得了下一项,则相当于执行
for
循环了 -
如果在获取下一项时遇到“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中可迭代对象的循环如何工作的”的一种基本方式,它本质上是Python中iter
和next
函数所定义的,Python中所有形式的迭代都由迭代器协议提供支持。
迭代器协议也被用于for
:
for n in numbers:
print(n)
多重赋值也使用迭代器协议:
x, y, z = coordinates
下面这种使用*
的表达式也使用迭代器协议:
a, b, *rest = numbers print(*numbers)
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!