3. 循环

你知道了如何在条件为真(或假)时执行操作,但如何重复操作多次呢?

简单的例子,假设要打印1~100的所有数。你可采用笨办法。
print(1)
print(2)
print(3)
...
print(99)
print(100)
但你愿意使用笨办法吗?
循环语句流程结构:

在这里插入图片描述

1. while循环

#为避免前述示例所示的繁琐代码,能够像下面这样做很有帮助:
#打印1~100之间的所有整数
x = 1
while x <= 100:
print(x)
x += 1
#还可以使用循环来确保用户输入名字,如下所示:
name = ''
while not name:
    name = input('Please enter your name: ')
print('Hello, {}!'.format(name))

2. for循环

'''
	while 语句非常灵活,可用于在条件为真时反复执行代码块。这在通常情况下很好,但有时候你可能想根据需要进行定制。
一种这样的需求是为序列(或其他可迭代对象)中每个元素执行代码块。
'''
#访问序列元素方式一:
words = ['this', 'is', 'an', 'ex', 'parrot']
for word in words:
    print(word)
#访问序列元素方式二:
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
for number in numbers:
    print(number)

#鉴于迭代(也就是遍历)特定范围内的数是一种常见的任务,Python提供了一个创建范围的内置函数。
>>> range(0, 10)
range(0, 10)
>>> list(range(0, 10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
'''
	范围类似于切片。它们包含起始位置(这里为0),但不包含结束位置(这里为10)。在很多情况下,你都希望范围的起始位置
为0。实际上,如果只提供了一个位置,将把这个位置视为结束位置,并假定起始位置为0。
'''
>>> range(10)
range(0, 10)

#下面的程序打印数1~100:
for number in range(1,101):
	print(number)
#注意:相比前面使用的 while 循环,这些代码要紧凑得多。

3. 迭代字典

#要遍历字典的所有关键字,可像遍历序列那样使用普通的 for 语句。
d = {'x': 1, 'y': 2, 'z': 3}
for key in d:
    print(key, 'corresponds to', d[key])
'''
	也可使用 keys 等字典方法来获取所有的键。如果只对值感兴趣,可使用 d.values 。你可能还记得, d.items 以元组的方式
返回键值对。 for 循环的优点之一是,可在其中使用序列解包。
'''
for key, value in d.items():
	print(key, 'corresponds to', value)

4. 迭代工具

'''
1. 并行迭代
	同时迭代俩序列:name和age,如果要打印名字和对应的年龄,可以像下面这样做:
'''
names = ['anne', 'beth', 'george', 'damon']
ages = [12, 45, 32, 102]
for i in range(len(names)):
	print(names[i], 'is', ages[i], 'years old')
'''
	i 是用作循环索引的变量的标准名称。一个很有用的并行迭代工具是内置函数 zip ,它将两个序列“缝合”起来,并返回一个由元组
组成的序列。返回值是一个适合迭代的对象,要查看其内容,可使用 list 将其转换为列表。
'''
>>> list(zip(names, ages))
[('anne', 12), ('beth', 45), ('george', 32), ('damon', 102)]
#“缝合”后,可在循环中将元组解包。
for name, age in zip(names, ages):
	print(name, 'is', age, 'years old')
#函数 zip 可用于“缝合”任意数量的序列。需要指出的是,当序列的长度不同时,函数 zip 将在最短的序列用完后停止“缝合”。
>>> list(zip(range(5), range(100000000)))
[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]


'''
2. 迭代时获取索引
	在有些情况下,你需要在迭代对象序列的同时获取当前对象的索引。例如,你可能想替换一个字符串列表中所有包含子串 'xxx' 
的字符串。当然,完成这种任务的方法有很多,但这里假设你要像下面这样做:
'''
for string in strings:
    if 'xxx' in string:
        index = strings.index(string) # 在字符串列表中查找字符串
        strings[index] = '[censored]'
'''
	这可行,但替换前的搜索好像没有必要。另外,如果没有替换,搜索返回的索引可能不对(即返回的是该字符串首次出现处的
索引)。下面是一种更佳的解决方案:
'''
index = 0
for string in strings:
    if 'xxx' in string:
        strings[index] = '[censored]'
    index += 1
#这个解决方案虽然可以接受,但看起来也有点笨拙。另一种解决方案是使用内置函数enumerate 。
#enumerate函数让你能够迭代索引值对,其中的索引是自动提供的。
for index, string in enumerate(strings):
    if 'xxx' in string:
        strings[index] = '[censored]'
'''
3. 反向迭代和排序后再迭代
	来看另外两个很有用的函数: reversed 和 sorted 。它们类似于列表方法 reverse 和 sort(sorted接受的参数也与 
sort 类似),但可用于任何序列或可迭代的对象,且不就地修改对象,而是返回反转和排序后的版本。
'''
>>> sorted([4, 3, 6, 8, 3])
[3, 3, 4, 6, 8]
>>> sorted('Hello, world!')
[' ', '!', ',', 'H', 'd', 'e', 'l', 'l', 'l', 'o', 'o', 'r', 'w']
>>> list(reversed('Hello, world!'))
['!', 'd', 'l', 'r', 'o', 'w', ' ', ',', 'o', 'l', 'l', 'e', 'H']
>>> ''.join(reversed('Hello, world!'))
'!dlrow ,olleH'
'''
	请注意, sorted 返回一个列表,而 reversed 像 zip 那样返回一个更神秘的可迭代对象。你无需关心这到底意味着什么,
只管在 for 循环或 join 等方法中使用它,不会有任何问题。只是你不能对它执行索引或切片操作,也不能直接对它调用列表的
方法。要执行这些操作,可先使用 list 对返回的对象进行转换。
'''

5. 跳出循环

	通常,循环会不断地执行代码块,直到条件为假或使用完序列中的所有元素。但在有些情况下,你可能想中断循环、开始新迭代
(进入“下一轮”代码块执行流程)或直接结束循环。
'''
1. break
	要结束(跳出)循环,可使用 break 。假设你要找出小于100的最大平方值(整数与自己相乘的结果),可从100开始向下迭代。
找到一个平方值后,无需再迭代,因此直接跳出循环。
'''
from math import sqrt
for n in range(99, 0, -1):
    root = sqrt(n)
    if root == int(root):
        print(n)
        break
'''
	如果你运行这个程序,它将打印81并结束。注意到我向 range 传递了第三个参数——步长,即序列中相邻数的差。通过将步长设
置为负数,可让 range 向下迭代,如上面的示例所示;还可让它跳过一些数:
'''
>>> range(0, 10, 2)
[0, 2, 4, 6, 8]

'''
2. continue
	语句 continue 没有 break 用得多。它结束当前迭代,并跳到下一次迭代开头。这基本上意味着跳过循环体中余下的语句,
但不结束循环。这在循环体庞大而复杂,且存在多个要跳过它的原因时很有用。在这种情况下,可使用 continue ,如下所示:
'''
for x in seq:
    if condition1: continue
    if condition2: continue
    if condition3: continue
    do_something()
    do_something_else()
    do_another_thing()
    etc()
# 然而,在很多情况下,使用一条 if 语句就足够了。
for x in seq:
    if not(condition1 or condition2 or condition3):
        do_something()
        do_something_else()
        do_another_thing()
        etc()
#continue 虽然是一个很有用的工具,但并非不可或缺的。然而,你必须熟悉 break 语句,因为在 while True 循环中经常用到。
'''
3. while True/break 成例
	在Python中, for 和 while 循环非常灵活,但偶尔遇到的一些问题可能让你禁不住想:如果这些循环的功能更强些就好了。
例如,假设你要在用户根据提示输入单词时执行某种操作,并在用户没有提供单词时结束循环。为此,一种办法如下:
'''
while True:
    word = input('Please enter a word: ')
    if not word: break
    # 使用这个单词做些事情:
    print('The word was ', word)
'''
6. 循环中的 else 子句
	通常,在循环中使用 break 是因为你“发现”了什么或“出现”了什么情况。要在循环提前结束时采取某种措施很容易,但有时候
你可能想在循环正常结束时才采取某种措施。如何判断循环是提前结束还是正常结束的呢?可在循环开始前定义一个布尔变量并将其
设置为 False ,再在跳出循环时将其设置为 True 。这样就可在循环后面使用一条 if 语句来判断循环是否是提前结束的。
'''
broke_out = False
for x in seq:
	do_something(x)
	if condition(x):
		broke_out = True
		break
	do_something_else(x)
if not broke_out:
	print("I didn't break out!")
# 一种更简单的办法是在循环中添加一条 else 子句,它仅在没有调用 break 时才执行。继续前面讨论 break 时的示例。
from math import sqrt
for n in range(99, 81, -1):
    root = sqrt(n)
    if root == int(root):
        print(n)
        break
else:
    print("Didn't find it!")

6. pass 、 del 和 exec

'''
1. 什么也不做
	什么也不做,情况不多,但一旦遇到,知道可使用 pass 语句大有裨益。
'''
>>> pass
>>>
'''
这里什么都没有发生。
	那么为何需要一条什么都不做的语句呢?在你编写代码时,可将其用作占位符。例如,你可能编写了一条 if 语句并想尝试
运行它,但其中缺少一个代码块,如下所示:
'''
if name == 'Ralph Auldus Melish':
    print('Welcome!')
elif name == 'Enid':
    # 还未完成……
elif name == 'Bill Gates':
    print('Access Denied')
#这些代码不能运行,因为在Python中代码块不能为空。要修复这个问题,只需在中间的代码块中添加一条 pass 语句即可。
if name == 'Ralph Auldus Melish':
    print('Welcome!')
elif name == 'Enid':
    # 还未完成……
    pass
elif name == 'Bill Gates':
    print('Access Denied')

'''
2. 使用 del 删除
对于你不再使用的对象,Python通常会将其删除(因为没有任何变量或数据结构成员指向它)。
'''
>>> scoundrel = {'age': 42, 'first name': 'Robin', 'last name': 'of Locksley'}
>>> robin = scoundrel
>>> scoundrel
{'age': 42, 'first name': 'Robin', 'last name': 'of Locksley'}
>>> robin
{'age': 42, 'first name': 'Robin', 'last name': 'of Locksley'}
>>> scoundrel = None
>>> robin
{'age': 42, 'first name': 'Robin', 'last name': 'of Locksley'}
>>> robin = None
'''
	最初, robin 和 scoundrel 指向同一个字典,因此将 None 赋给 scoundrel 后,依然可以通过 robin来访问这个字典。
但将 robin 也设置为 None 之后,这个字典就漂浮在计算机内存中,没有任何名称与之相关联,再也无法获取或使用它了。因此
,智慧无穷的Python解释器直接将其删除。这被称为垃圾收集。请注意,在前面的代码中,也可将其他任何值(而不是 None )
赋给两个变量,这样字典也将消失。

	另一种办法是使用 del 语句。(第2章和第4章使用这条语句来删除序列和字典,还记得吗?)这不仅会删除到对象的引用,
还会删除名称本身。
'''
>>> x = 1
>>> del x
>>> x
Traceback (most recent call last):
File "<pyshell#255>", line 1, in ?
x
NameError: name 'x' is not defined

#这看似简单,但有时不太好理解。例如,在下面的示例中, x 和 y 指向同一个列表:
>>> x = ["Hello", "world"]
>>> y = x
>>> y[1] = "Python"
>>> x
['Hello', 'Python']
#你可能认为通过删除 x ,也将删除 y ,但情况并非如此。
>>> del x
>>> y
['Hello', 'Python']
#这是为什么呢? x 和 y 指向同一个列表,但删除 x 对 y 没有任何影响,因为你只删除名称 x ,而没有删除列表本身(值)。
#事实上,在Python中,根本就没有办法删除值,而且你也不需要这样做,因为对于你不再使用的值,Python解释器会立即将其删除。

'''
3. 使用 exec 和 eval 执行字符串及计算其结果
	有时候,你可能想动态地编写Python代码,并将其作为语句进行执行或作为表达式进行计算。这可能犹如黑暗魔法,一定要小心。
 exec 和 eval 现在都是函数,但 exec 以前是一种语句,而 eval与它紧密相关。这就是我在这里讨论它们的原因所在。

3.1 1. exec
	函数 exec 将字符串作为代码执行
'''
>>> exec("print('Hello, world!')")
Hello, world!
'''
	然而,调用函数 exec 时只给它提供一个参数绝非好事。在大多数情况下,还应向它传递一个命名空间——用于放置变量的地方;
否则代码将污染你的命名空间,即修改你的变量。例如,假设代码使用了名称 sqrt ,结果将如何呢?
'''
>>> from math import sqrt
>>> exec("sqrt = 1")
>>> sqrt(4)
Traceback (most recent call last):
	File "<pyshell#18>", line 1, in ?
		sqrt(4)
TypeError: object is not callable: 1
'''
	既然如此,为何要将字符串作为代码执行呢?函数 exec 主要用于动态地创建代码字符串。如果这种字符串来自其他地方(可能
是用户),就几乎无法确定它将包含什么内容。因此为了安全起见,要提供一个字典以充当命名空间。
'''
#为此,你添加第二个参数——字典,用作代码字符串的命名空间
>>> from math import sqrt
>>> scope = {}
>>> exec('sqrt = 1', scope)
>>> sqrt(4)
2.0
>>> scope['sqrt']
1
'''
	如你所见,可能带来破坏的代码并非覆盖函数 sqrt 。函数 sqrt 该怎样还怎样,而通过 exec 执行赋值语句创建的变量位于
 scope 中。请注意,如果你尝试将 scope 打印出来,将发现它包含很多内容,这是因为自动在其中添加了包含所有内置函数和值
 的字典 __builtins__ 。
'''
>>> len(scope)
2
>>> scope.keys()
['sqrt', '__builtins__']




'''
3.2 eval
	eval 是一个类似于 exec 的内置函数。 exec 执行一系列Python语句,而 eval 计算用字符串表示的Python表达式的值,并
返回结果( exec 什么都不返回,因为它本身是条语句)。例如,你可使用如下代码来创建一个Python计算器:
'''
>>> eval(input("Enter an arithmetic expression: "))
Enter an arithmetic expression: 6 + 18 * 2
42
#与 exec 一样,也可向 eval 提供一个命名空间,虽然表达式通常不会像语句那样给变量重新赋值。
#向 exec 或 eval 提供命名空间时,可在使用这个命名空间前在其中添加一些值。
>>> scope = {}
>>> scope['x'] = 2
>>> scope['y'] = 3
>>> eval('x * y', scope)
6

#同样,同一个命名空间可用于多次调用 exec 或 eval 。
>>> scope = {}
>>> exec('x = 2', scope)
>>> eval('x * x', scope)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章