《Python基础教程》第5章 条件、循环及其他语句

你现在肯定有点不耐烦了。这些数据类型确实好,可你却没法使用它们来做什么,不是吗?
下面加快点速度。你已见过几种语句( print语句、import语句和赋值语句),先来看看这些语句的其他一些用法,再深入探讨条件语句循环语句。然后,我们将介绍列表推导,它们虽然是表达式,但工作原理几乎与条件语句和循环语句相同。最后,我们将介绍pass、 del和exec。
5.1 再谈 print 和 import
随着你对Python的认识越来越深入,可能发现有些你自以为很熟悉的方面隐藏着让人惊喜的特性。下面就来看看print和import隐藏的几个特性。虽然print现在实际上是一个函数,但以前却是一种语句,因此在这里进行讨论。

5.1.1 打印多个参数
你知道, print可用于打印一个表达式,这个表达式要么是字符串,要么将自动转换为字符
串。但实际上,你可同时打印多个表达式,条件是用逗号分隔它们:

>>> print('Age:',32)
Age: 32

如你所见,在参数之间插入了一个空格字符。在你要合并文本和变量值,而又不想使用字符
串格式设置功能时,这种行为很有帮助。

 如果字符串变量greeting不包含逗号,如何在结果中添加呢?你不能像下面这样做:
print(greeting, ',', salutation, name)
因为这将在逗号前添加一个空格。下面是一种可行的解决方案:

它将逗号和变量greeting相加。如果需要,可自定义分隔符:

 print('Hello,', end='')
print('world!')

上述代码打印Hello, world!①。

① 仅当这些代码包含在脚本中时才如此。在交互式Python会话中,将分别执行每条语句并打印其内容。
5.1.2 导入时重命名
从模块导入时,通常使用
import somemodule
或使用
from somemodule import somefunction

from somemodule import somefunction, anotherfunction, yetanotherfunction

from somemodule import *
仅当你确定要导入模块中的一切时,采用使用最后一种方式。但如果有两个模块,它们都包含函数open,该如何办呢?你可使用第一种方式导入这两个模块,并像下面这样调用函数:
module1.open(...)
module2.open(...)

但还有一种办法:在语句末尾添加as子句并指定别名。下面是一个导入整个模块并给它指定别名的例子:

 对于前面的函数open,可像下面这样导入它们:
from module1 import open as open1
from module2 import open as open2

 5.2 赋值魔法
即便是不起眼的赋值语句也蕴藏着一些使用窍门。
5.2.1 序列解包
赋值语句你见过很多,有的给变量赋值,还有的给数据结构的一部分(如列表中的元素和切片,或者字典项)赋值,但还有其他类型的赋值语句。例如,可同时(并行)给多个变量赋值:

 实际上,这里执行的操作称为序列解包(或可迭代对象解包):将一个序列(或任何可迭代
对象)解包,并将得到的值存储到一系列变量中。下面用例子进行解释。

 这在使用返回元组(或其他序列或可迭代对象)的函数或方法时很有用。假设要从字典中随便获取(或删除)一个键值对,可使用方法popitem,它随便获取一个键值对并以元组的方式返回。接下来,可直接将返回的元组解包到两个变量中。

 这让函数能够返回被打包成元组的多个值,然后通过一条赋值语句轻松地访问这些值。要解包的序列包含的元素个数必须与你在等号左边列出的目标个数相同,否则Python将引发异常。

 可使用星号运算符( *)来收集多余的值,这样无需确保值和变量的个数相同,如下例所示:

 赋值语句的右边可以是任何类型的序列,但带星号的变量最终包含的总是一个列表。在变量和值的个数相同时亦如此。

 这种收集方式也可用于函数参数列表中(参见第6章)。
5.2.2 链式赋值
链式赋值是一种快捷方式,用于将多个变量关联到同一个值。这有点像前一节介绍的并行赋值,但只涉及一个值:
x = y = somefunction()
上述代码与下面的代码等价:
y = somefunction()
x = y
请注意,这两条语句可能与下面的语句不等价:
x = somefunction()
y = somefunction()
有关这方面的详细信息,请参阅5.4.6节介绍相同运算符( is)的部分。
5.2.3 增强赋值
可以不编写代码x = x + 1,而将右边表达式中的运算符(这里是+)移到赋值运算符( =)的前面,从而写成x += 1。这称为增强赋值,适用于所有标准运算符,如*、 /、 %等。

 通过使用增强赋值,可让代码更紧凑、更简洁,同时在很多情况下的可读性更强。
5.3 代码块:缩进的乐趣
代码块其实并不是一种语句,但要理解接下来两节的内容,你必须熟悉代码块。
代码块是一语句,可在满足条件时执行( if语句),可执行多次(循环),等等。代码块是通过缩进代码(即在前面加空格)来创建的。

 在同一个代码块中,各行代码的缩进量必须相同。下面的伪代码(并非真正的Python代码)演示了如何缩进:
this is a line
this is another line:
        this is another block
        continuing the same block
        the last line of this block
phew, there we escaped the inner block
在很多语言中,都使用一个特殊的单词或字符(如begin或{)来标识代码块的起始位置,并使用另一个特殊的单词或字符(如end或})来标识结束位置。在Python中,使用冒号( :)指出接下来是一个代码块,并将该代码块中的每行代码都缩进相同的程度。发现缩进量与之前相同时,你就知道当前代码块到此结束了。(很多用于编程的编辑器和IDE知道如何缩进代码块, 可帮助你轻松地正确缩进。)
下面来看看代码块的用途。

5.4 条件和条件语句
到目前为止,在你编写的程序中,语句都是逐条执行的。现在更进一步,让程序选择是否执
行特定的语句块。
5.4.1 这正是布尔值的用武之地
在本书前面,你多次遇到了真值,现在终于需要用到它们了。真值也称布尔值,是以在真值
方面做出了巨大贡献的George Boole命名的。

 用作布尔表达式(如用作if语句中的条件)时,下面的值都将被解释器视为
False None 0 "" () [] {}
换而言之,标准值False和None、各种类型(包括浮点数、复数等)的数值0、空序列(如空
字符串、空元组和空列表)以及空映射(如空字典)都被视为假,而其他各种值都被视为真①,
包括特殊值True②。
明白了吗?这意味着任何Python值都可解释为真值。乍一看这有点令人迷惑,但也很有用。虽然可供选择的真值非常多,但标准真值为True和False。在有些语言(如C语言和2.3之前的Python
版本)中,标准真值为0(表示)和1(表示)。实际上, True和False不过是0和1的别名,虽
然看起来不同,但作用是相同的。

 因此,如果你看到一个返回1或0的表达式(可能是使用较旧的Python版本编写的),就知道
这实际上意味着True或False。
布尔值True和False属于类型bool,而bool与list、 str和tuple一样,可用来转换其他的值。

 鉴于任何值都可用作布尔值,因此你几乎不需要显式地进行转换( Python会自动转换)。

 5.4.2 有条件地执行和 if 语句
真值可合并,至于如何合并稍后再讲,先来看看真值可用来做什么。请尝试运行下面的脚本:

 这就是if语句,让你能够有条件地执行代码。这意味着如果条件( if和冒号之间的表达式)为前面定义的,就执行后续代码块(这里是一条print语句);如果条件为,就不执行(你应该猜到了)。

5.4.3 else 子句
在前一节的示例中,如果你输入以Gumby结尾的名字,方法name.endswith将返回True,导致
后续代码块执行——打印问候语。如果你愿意,可使用else子句增加一种选择(之所以叫子句是
因为else不是独立的语句,而是if语句的一部分)。
 

#chapter05_1.py

name = input('What is your name?')
if name.endswith("Gumby"):
    print('Hello, Mr.Gumby')
else:
    print('Hello, stranger')

运行结果:

在这里,如果没有执行第一个代码块(因为条件为假),将进入第二个代码块。这个示例表明, Python代码很容易理解,不是吗?如果从if开始将代码大声朗读出来,听起来将像普通句子一样(也可能不那么普通)
还有一个与if语句很像的“亲戚”,它就是条件表达式——C语言中三目运算符的Python版本。下面的表达式使用if和else确定其值:

status = "friend" if name.endswith("Gumby") else "stranger"
如果条件(紧跟在if后面)为真,表达式的结果为提供的第一个值(这里为"friend"),否则为第二个值(这里为"stranger")。
5.4.4 elif 子句
要检查多个条件,可使用elif。 elif是else if的缩写,由一个if子句和一个else子句组合而成,也就是包含条件的else子句。

# chapter05_3.py
num = int(input('Enter a number'))

if num > 0:
    print("The number is positive")
elif num < 0:
    print('The number is negative')
else:
    print('The number is zero')

运行结果:

 5.4.5 代码块嵌套
下面穿插点额外的内容。你可将if语句放在其他if语句块中,如下所示:
 

# chapter05_3.py
name = input('What is your name?')
if name.endswith('Gumby'):
    if name.startswith('Mr.'):
        print('Hello, Mr.Gumby')
    elif name.startswith('Mrs.'):
        print('Hello, Mrs.Gumby')
    else:
        print('Hello, Gumby')
else:
    print('Hello, stranger')

运行结果:

 在这里,如果名字以Gumby结尾,就同时检查名字开头,这是在第一个代码块中使用一条独立的if语句完成的。请注意,这里还使用了elif。最后一个分支( else子句)没有指定条件——如果没有选择其他分支,就选择最后一个分支。如果需要,这里的两个else子句都可省略。如果省略里面的else子句,将忽略并非以Mr.或Mrs.打头的名字(假设名字为Gumby)。如果省略外面的else句,将忽略陌生人。
5.4.6 更复杂的条件
这就是你需要知道的有关if语句的全部知识。下面来说说条件本身,因为它们是有条件执行中最有趣的部分。
1. 比较运算符
在条件表达式中,最基本的运算符可能是比较运算符,它们用于执行比较。表5-1对比较运
算符做了总结。

 与赋值一样, Python也支持链式比较:可同时使用多个比较运算符,如0 < age < 100。有些比较运算符需要特别注意,下面就来详细介绍。
相等运算符
要确定两个对象是否相等,可使用比较运算符,用两个等号( ==)表示。

 两个等号?为何不像数学中那样使用一个等号呢?相信你很聪明,自己就能够明白其中的原因,但这里还是试试一个等号吧。

 一个等号是赋值运算符,用于修改值,而进行比较时你可不想这样做。
is:相同运算符
这个运算符很有趣,其作用看似与==一样,但实际上并非如此。

 在前几个示例中,看不出什么问题,但最后一个示例的结果很奇怪: x和z相等,但x is z的结果却为False。为何会这样呢?因为is检查两个对象是否相同(而不是相等)。变量x和y指向同一个列表,而z指向另一个列表(其中包含的值以及这些值的排列顺序都与前一个列表相同)。这两个列表虽然相等,但并非同一个对象。
这好像不可理喻?请看下面的示例:

 在这个示例中,我首先创建了两个不同的列表x和y。如你所见, x is not y(与x is y相反)的结果为True,这一点你早已知道。接下来,我稍微修改了这两个列表,现在它们虽然相等,但依然是两个不同的列表。

 显然,这两个列表相等但不相同。
总之, ==用来检查两个对象是否相等,而is用来检查两个对象是否相同(是同一个对象)。
 

 in:成员资格运算符
运算符in在2.2.5节介绍过,与其他比较运算符一样,它也可用于条件表达式中。
 

# chapter05_4.py
name = input('What is your name? ')
if 's' in name:
    print('Your name contains the letter "s".')
else:
    print('Your name does not contain the letter "s".')

运行结果:

 字符串和序列的比较
字符串是根据字符的字母排列顺序进行比较的。
>>> "alpha" < "beta"
True

虽然基于的是字母排列顺序,但字母都是Unicode字符,它们是按码点排列的。

实际上,字符是根据顺序值排列的。要获悉字母的顺序值,可使用函数ord。这个函数的作用与函数chr相反:

 

 这种方法既合理又一致,但可能与你排序的方式相反。例如,涉及大写字母时,排列顺序就可能与你想要的不同。

2. 布尔运算符
至此,你已见过很多返回真值的表达式(实际上,考虑到所有值都可解释为真值,因此所有的表达式都返回真值),但你可能需要检查多个条件。例如,假设你要编写一个程序,让它读取一个数,并检查这个数是否位于1~ 10(含)。为此,可像下面这样做:
 

# chapter05_5.py
number = int(input('Enter a number between 1 and 10: '))

if number <= 10:
    if number >= 1:
        print('Great!')
    else:
        print('Wrong!')
else:
    print('Wrong!')

运行结果:

这可行,但有点笨拙,因为你输入了print('Wrong!')两次。重复劳动可不是好事,那么该如何办呢?很简单。
 

# chapter05_6.py
number = int(input('Enter a number between 1 and 10: '))
if number <= 10 and number >= 1:
    print('Great!')
else:
    print('Wrong!')

运行结果:

 注意 通过使用链式比较1 <= number <= 10可进一步简化这个示例。也许原本就应该这样做。
运算符and是一个布尔运算符。它接受两个真值,并在这两个值都为真时返回真,否则返回假。还有另外两个布尔运算符: or和not。通过使用这三个运算符,能以任何方式组合真值。
if ((cash > price) or customer_has_good_credit) and not out_of_stock:
        give_goods()

 5.4.7 断言
if语句有一个很有用的“亲戚”,其工作原理类似于下面的伪代码:
if not condition:
        crash program

      问题是,为何要编写类似于这样的代码呢?因为让程序在错误条件出现时立即崩溃胜过以后再崩溃。基本上,你可要求某些条件得到满足(如核实函数参数满足要求或为初始测试和调试提供帮助),为此可在语句中使用关键字assert。


如果知道必须满足特定条件,程序才能正确地运行,可在程序中添加assert语句充当检查点,这很有帮助。
还可在条件后面添加一个字符串,对断言做出说明。

 

 5.5 循环
至此,你知道了如何在条件为真(或假)时执行操作,但如何重复操作多次呢?例如,你可能想创建一个程序,每月都提醒支付房租。如果只使用已介绍过的工具,必须像下面这样编写这个程序(伪代码):
send mail
wait one month send mail
wait one month send mail
wait one month
(... and so on)

但是如果希望程序这样不断执行下去,直到人为停止,该如何办呢?基本上,你需要编写类似于下面的代码(也是伪代码):
while we aren't stopped:
send mail
wait one month

再来看一个更简单的例子,假设要打印1~ 100的所有数。同样,你可采用笨办法。
print(1)
print(2)
print(3)
...
print(99)
print(100)

但如果你愿意使用笨办法,就不会求助于Python了,不是吗?
5.5.1 while 循环
为避免前述示例所示的繁琐代码,能够像下面这样做很有帮助:
 

# chapter05_7.py
x = 1
while x <= 100:
    print(x)
    x += 1

输出

1
2
3
.
.
.
100

那么如何使用Python来实现的?你猜对了,就像上面那样做。不太复杂,不是吗?你还可以使用循环来确保用户输入名字,如下所示:
 

# chapter05_7.py
name = ''
while not name:
    name = input('Please enter your name: ')
print('Hello, {}!'.format(name))

运行结果:

 请尝试运行这些代码,并在要求你输入名字时直接按回车键。你会看到提示信息再次出现,因为name还是为空字符串,这相当于假。

 5.5.2 for 循环
while语句非常灵活,可用于在条件为真时反复执行代码块。这在通常情况下很好,但有时候你可能想根据需要进行定制。一种这样的需求是为序列(或其他可迭代对象)中每个元素执行代码块。

 为此,可使用for语句:

 或
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
for number in numbers:
print(number)

鉴于迭代(也就是遍历)特定范围内的数是一种常见的任务, Python提供了一个创建范围的内置函数。

 范围类似于切片。它们包含起始位置(这里为0),但不包含结束位置(这里为10)。在很多情况下,你都希望范围的起始位置为0。实际上,如果只提供了一个位置,将把这个位置视为结束位置,并假定起始位置为0。

 注意,相比前面使用的while循环,这些代码要紧凑得多。
提示 只要能够使用for循环,就不要使用while循环。

5.5.3 迭代字典
要遍历字典的所有关键字,可像遍历序列那样使用普通的for语句。

 也可使用keys等字典方法来获取所有的键。如果只对值感兴趣,可使用d.values。你可能还记得, d.items以元组的方式返回键值对。 for循环的优点之一是,可在其中使用序列解包。

 5.5.4 一些迭代工具
Python提供了多个可帮助迭代序列(或其他可迭代对象)的函数,其中一些位于第10章将介绍的模块itertools中,但还有一些内置函数使用起来也很方便。
1. 并行迭代
有时候,你可能想同时迭代两个序列。假设有下面两个列表:

 i是用作循环索引的变量的标准名称。一个很有用的并行迭代工具是内置函数zip,它将两个序列“缝合”起来,并返回一个由元组组成的序列。返回值是一个适合迭代的对象,要查看其内容,可使用list将其转换为列表。

 函数zip可用于“缝合”任意数量的序列。需要指出的是,当序列的长度不同时,函数zip将在最短的序列用完后停止“缝合”。
>>> list(zip(range(5), range(100000000)))
[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]

2. 迭代时获取索引
在有些情况下,你需要在迭代对象序列的同时获取当前对象的索引。例如,你可能想替换一个字符串列表中所有包含子串'xxx'的字符串。当然,完成这种任务的方法有很多,但这里假设你要像下面这样做:

# chapter05_9.py
strings = ['apple', 'pear', 'peach', 'pineapple', 'apricot']

for string in strings:
    if 'ea' in string:
        index = strings.index(string) #在字符串列表中查找字符串
        strings[index] = '[censored]'
        print('strings[' + str(index) + ']=' + strings[index])
for value in strings:
    print(value)

运行结果:

 这可行,但替换前的搜索好像没有必要。另外,如果没有替换,搜索返回的索引可能不对(即返回的是该字符串首次出现处的索引)。下面是一种更佳的解决方案:
 

# chapter05_10.py
strings = ['apple', 'pear', 'peach', 'pineapple', 'apricot']

index = 0
for string in strings:
    if 'ea' in string:
        strings[index] = 'censored'
    index += 1

for value in strings:
    print(value)

 运行结果:

这个解决方案虽然可以接受,但看起来也有点笨拙。另一种解决方案是使用内置函数enumerate。
 

# chapter05_11.py
strings = ['apple', 'pear', 'peach', 'pineapple', 'apricot']

for index, string in enumerate(strings):
    if 'ea' in string:
        strings[index] = '[censored]'

for value in strings:
    print(value)

运行结果:

 这个函数让你能够迭代索引-值对,其中的索引是自动提供的。
3. 反向迭代和排序后再迭代
来看另外两个很有用的函数: reversed和sorted。它们类似于列表方法reverse和sort( sorted接受的参数也与sort类似),但可用于任何序列或可迭代的对象,且不就地修改对象,而是返回反转和排序后的版本。

 请注意, sorted返回一个列表,而reversed像zip那样返回一个更神秘的可迭代对象。你无需关心这到底意味着什么,只管在for循环或join等方法中使用它,不会有任何问题。只是你不能对它执行索引或切片操作,也不能直接对它调用列表的方法。要执行这些操作,可先使用list对返回的对象进行转换。

 5.5.5 跳出循环
通常,循环会不断地执行代码块,直到条件为假或使用完序列中的所有元素。但在有些情况下,你可能想中断循环、开始新迭代(进入“下一轮”代码块执行流程)或直接结束循环。
1. break
要结束(跳出)循环,可使用break。假设你要找出小于100的最大平方值(整数与自己相乘的结果),可从100开始向下迭代。找到一个平方值后,无需再迭代,因此直接跳出循环。
 

# chapter05_12.py
from math import sqrt
for n in range(99, 0, -1):
    root = sqrt(n)
    if root == int(root):
        print(n)
        break

运行结果:

 如果你运行这个程序,它将打印81并结束。注意到我向range传递了第三个参数——步长,即序列中相邻数的差。通过将步长设置为负数,可让range向下迭代,如上面的示例所示;还可让它跳过一些数:

 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循环非常灵活,但偶尔遇到的一些问题可能让你禁不住想:如果这
些循环的功能更强些就好了。例如,假设你要在用户根据提示输入单词时执行某种操作,并在用
户没有提供单词时结束循环。为此,一种办法如下:

# chapter05_13.py
word = 'dummy'
while word:
    word = input('Please enter a world:')
    # 使用这个单词做些事情:
    print('The word was', word)

运行结果:

这与你希望的一致,但你可能想使用单词做些比打印它更有用的事情。然而,如你所见,这些代码有点难看。为进入循环,你需要将一个哑值(未用的值)赋给word。像这样的哑值通常昭示着你的做法不太对。下面来尝试消除这个哑值。
 

# chapter05_14.py
word = input('Please enter a word:')
while word:
    # 使用这个单词做些事情:
    print('The word was ', word)
    word = input('Please enter a word:')

运行结果:

哑值消除了,但包含重复的代码(这样也不好):需要在两个地方使用相同的赋值语句并调用input。如何避免这样的重复呢?可使用成例while True/break。
 

# chapter05_15.py
while True:
    word = input('Please enter a word:')
    if not word: break
    # 使用这个单词做些事情:
    print('The world was ', word)

 运行结果:

 while True导致循环永不结束,但你将条件放在了循环体内的一条if语句中,而这条if语句将在条件满足时调用break。这说明并非只能像常规while循环那样在循环开头结束循环,而是可在循环体的任何地方结束循环。 if/break行将整个循环分成两部分:第一部分负责设置(如果使用常规while循环,将重复这部分),第二部分在循环条件为真时使用第一部分初始化的数据。
虽然应避免在代码中过多使用break(因为这可能导致循环难以理解,在一个循环中包含多个break时尤其如此),但这里介绍的技巧很常见,因此大多数Python程序员(包括你自己)都能够明白你的意图。

5.5.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时的示例。

# chapter05_16.py
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")

运行结果:

 请注意,为测试else子句,我将下限改成了81(不包含)。如果你运行这个程序,它将打印"Didn't find it!",因为正如你在前面讨论break时看到的,小于100的最大平方值为81。无论是在for循环还是while循环中,都可使用continue、 break和else子句。
5.6 简单推导
列表推导是一种从其他列表创建列表的方式,类似于数学中的集合推导。列表推导的工作原理非常简单,有点类似于for循环。

 

 作为对比,下面的两个for循环创建同样的列表:
 

# chapter05_17.py
result = []
for x in range(3):
    for y in range(3):
        result.append((x, y))
for value in result:
    print(value)

运行结果:

 与以前一样,使用多个for部分时,也可添加if子句。

 这些代码将名字的首字母相同的男孩和女孩配对。
                                                        更佳的解决方案

前述男孩/女孩配对示例的效率不太高,因为它要检查每种可能的配对。使用Python解决这个问题的方法有很多,下面是Alex Martelli推荐的解决方案:
 

# chapter05_18.py
girls = ['alice', 'bernice', 'clarice']
boys = ['chris', 'arnold', 'bob']
letterGirls = {}
for girl in girls:
    letterGirls.setdefault(girl[0], []).append(girl)
print([b + '+' + g for b in boys for g in letterGirls[b[0]]])

运行结果:

 这个程序创建一个名为letterGirls的字典,其中每项的键都是一个字母,而值为以这个字母打头的女孩名字组成的列表(字典方法setdefault在前一章介绍过)。创建这个字典后,列表推导遍历所有的男孩,并查找名字首字母与当前男孩相同的所有女孩。这样,这个列表推导就无需尝试所有的男孩和女孩组合并检查他们的名字首字母是否相同了。
使用圆括号代替方括号并不能实现元组推导,而是将创建生成器,详细信息请参阅第9章的旁注“简单生成器”。然而,可使用花括号来执行字典推导

 在列表推导中, for前面只有一个表达式,而在字典推导中, for前面有两个用冒号分隔的表达式。这两个表达式分别为键及其对应的值。
5.7 三人行
结束本章前,大致介绍一下另外三条语句: pass、 del和exec。
5.7.1 什么都不做
有时候什么都不用做。这种情况不多,但一旦遇到,知道可使用pass语句大有裨益。
>>> pass
>>>

这里什么都没有发生。
那么为何需要一条什么都不做的语句呢?在你编写代码时,可将其用作占位符。例如,你可
能编写了一条if语句并想尝试运行它,但其中缺少一个代码块,如下所示:
if name == 'Ralph Auldus Melish':
        print('Welcome!')
elif name == 'Enid':
        # 还未完成……
elif name == 'Bill Gates':
        print('Access Denied')

这些代码不能运行,因为在Python中代码块不能为空。要修复这个问题,只需在中间的代码
块中添加一条pass语句即可。

# chapter05_19.py
name = "Enid"

if name == 'Ralph Auldus Melish':
    print('Welcome!')
elif name == 'Enid':
    #还未完成
    pass
elif name == 'Bill Gates':
    print('Access Denied')

 5.7.2 使用 del 删除
对于你不再使用的对象, Python通常会将其删除(因为没有任何变量或数据结构成员指向它)。

 最初, robin和scoundrel指向同一个字典,因此将None赋给scoundrel后,依然可以通过robin来访问这个字典。但将robin也设置为None之后,这个字典就漂浮在计算机内存中,没有任何名称与之相关联,再也无法获取或使用它了。因此,智慧无穷的Python解释器直接将其删除。这被称为垃圾收集。请注意,在前面的代码中,也可将其他任何值(而不是None)赋给两个变量,这样字典也将消失。
另一种办法是使用del语句。(第2章和第4章使用这条语句来删除序列和字典,还记得吗?)这不仅会删除到对象的引用,还会删除名称本身。

 这看似简单,但有时不太好理解。例如,在下面的示例中, x和y指向同一个列表:

 这是为什么呢? x和y指向同一个列表,但删除x对y没有任何影响,因为你只删除名称x,而没有删除列表本身(值)。事实上,在Python中,根本就没有办法删除值,而且你也不需要这样做,因为对于你不再使用的值, Python解释器会立即将其删除。
5.7.3 使用 exec 和 eval 执行字符串及计算其结果
有时候,你可能想动态地编写Python代码,并将其作为语句进行执行或作为表达式进行计算。
这可能犹如黑暗魔法,一定要小心。 exec和eval现在都是函数,但exec以前是一种语句,而eval
与它紧密相关。这就是我在这里讨论它们的原因所在。

 1. exec
函数exec将字符串作为代码执行。

 然而,调用函数exec时只给它提供一个参数绝非好事。在大多数情况下,还应向它传递一个命名空间——用于放置变量的地方;否则代码将污染你的命名空间,即修改你的变量。例如,假设代码使用了名称sqrt,结果将如何呢?

 既然如此,为何要将字符串作为代码执行呢?函数exec主要用于动态地创建代码字符串。如果这种字符串来自其他地方(可能是用户),就几乎无法确定它将包含什么内容。因此为了安全起见,要提供一个字典以充当命名空间。

 为此,你添加第二个参数——字典,用作代码字符串的命名空间①。

 如你所见,可能带来破坏的代码并非覆盖函数sqrt。函数sqrt该怎样还怎样,而通过exec执行赋值语句创建的变量位于scope中。请注意,如果你尝试将scope打印出来,将发现它包含很多内容,这是因为自动在其中添加了包含所有内置函数和值的字典__builtins__。

 2. eval
eval是一个类似于exec的内置函数。 exec执行一系列Python语句,而eval计算用字符串表示的Python表达式的值,并返回结果( exec什么都不返回,因为它本身是条语句)。例如,你可使用如下代码来创建一个Python计算器:

 与exec一样,也可向eval提供一个命名空间,虽然表达式通常不会像语句那样给变量重新赋值。

                                                                 浅谈作用域

 向exec或eval提供命名空间时,可在使用这个命名空间前在其中添加一些值。

 5.8 小结
本章介绍了多种语句。
 打印语句:你可使用print语句来打印多个用逗号分隔的值。如果print语句以逗号结尾,
后续print语句将在当前行接着打印。
 导入语句:有时候,你不喜欢要导入的函数的名称——可能是因为你已将这个名称用作
他用。在这种情况下,可使用import ... as ...语句在本地重命名函数。
 赋值语句:通过使用奇妙的序列解包和链式赋值,可同时给多个变量赋值;而通过使用
增强赋值,可就地修改变量。
 代码块:代码块用于通过缩进将语句编组。代码块可用于条件语句和循环中,还可用于
函数和类定义中(这将在本书后面介绍)。
 条件语句:条件语句根据条件(布尔表达式)决定是否执行后续代码块。通过使用if/elif/
else,可将多个条件语句组合起来。条件语句的一个变种是条件表达式,如a if b else c。
 断言:断言断定某件事(一个布尔表达式)为真,可包含说明为何必须如此的字符串。
如果指定的表达式为假,断言将导致程序停止执行(或引发第8章将介绍的异常)。最好
尽早将错误揪出来,免得它潜藏在程序中,直到带来麻烦。
 循环:你可针对序列中的每个元素(如特定范围内的每个数)执行代码块,也可在条件
为真时反复执行代码块。要跳过代码块中余下的代码,直接进入下一次迭代,可使用
continue语句;要跳出循环,可使用break语句。另外,你还可在循环末尾添加一个else
子句,它将在没有执行循环中的任何break语句时执行。
 推导:推导并不是语句,而是表达式。它们看起来很像循环,因此我将它们放在循环中
讨论。通过列表推导,可从既有列表创建出新列表,这是通过对列表元素调用函数、剔
除不想要的函数等实现的。推导功能强大,但在很多情况下,使用普通循环和条件语句
也可完成任务,且代码的可读性可能更高。使用类似于列表推导的表达式可创建出字典。
 pass、 del、 exec和eval: pass语句什么都不做,但适合用作占位符。 del语句用于删除变
量或数据结构的成员,但不能用于删除值。函数exec用于将字符串作为Python程序执行。
函数eval计算用字符串表示的表达式并返回结果。
5.8.1 本章介绍的新函数
 

函数描述
chr(n)返回一个字符串,其中只包含一个字符,这个字符对应于传入的顺序值n( 0 ≤n < 256)
eval(source[,globals[,locals]])计算并返回字符串表示的表达式的结果
exec(source[,globals[,locals]])将字符串作为语句执行
enumerate(seq)生成可迭代的索引-值对
ord(c)接受一个只包含一个字符的字符串,并返回这个字符的顺序值(一个整数)
range([start,] stop[, step])创建一个由整数组成的列表
reversed(seq)按相反的顺序返回seq中的值,以便用于迭代
sorted(seq[,cmp[,key[,reverse])返回一个列表,其中包含seq中的所有值且这些值是经过排序的
xrange([start,] stop[, step])创建一个用于迭代的xrange对象
zip(seq1, seq2,...)创建一个适合用于并行迭代的新序列

5.8.2 预告
至此,你学完了基础知识,能够实现任何想象得到的算法,还能够读取参数并打印结果。在
接下来的两章中,你将学习抽象。在编写较大的程序时,抽象可避免你只见树木不见森林。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值