Python Chapter 5 条件、循环和其他语句

对于很多应用程序来说,使用 logging 模块日志比 print 语句更合适.

使用逗号输出

打印多个表达式时,使用逗号将他们隔开就好:

>>> print 'Age:', 32
Age: 32

注意: print 的参数并不能像我们预期那样构成一个元组:

>>> 1, 2, 3
(1, 2, 3)
>>> print 1, 3, 4
1 3 4
>>> print (1, 3, 4)
(1, 3, 4)

>>> name = 'Gumby'
>>> salutation = 'Mr'
>>> greeting = 'Hello'
>>> print greeting, salutation, name
Hello Mr Gumby
>>> print greeting + ',', salutation, name
Hello, Mr Gumby

如果在结尾处加上逗号,那么接下来的语句会与前一条语句在同一行打印,例如:

print 'hello.', 
print 'world!'

这只在脚本中有作用,交互式 Python 会话中则没有效果.

把某件事作为另一件事导入

从模块导入函数时,可以使用:

import somemodule
from somemodule import somefunction
from somemodule import somefunction, anotherfunction, yetanotherfunction
from somemodule import *

只有确定自己想要从给定的模块导入所有功能时,才使用最后一个版本.如果两个模块都有 open 函数,可以像下面这样使用函数:

module1.open(...)
module2.open(...)

也可以在语句末尾增加一个 as 子句,在该子句后给出名字,或为整个模块提供别名:

>>> import math as foobar
>>> foobar.sqrt(4)
2.0

也可以为函数提供别名:

>>> from math import sqrt as foobar
>>> foobar(4)
2.0

对于 open 函数,可以像下面这样使用:

from module1 import open as open1
from module2 import open as open2

注意:有些模块,例如 os.path 是分层次安排的(一个模块在另一个模块的内部).有关模块结构的更多信息,请参见第十章关于包的部分.

赋值魔法

序列解包

多个赋值操作可以同时进行:

>>> x, y, z = 1, 3, 4
>>> print x, y, z
1 3 4

用它交换两个(或多个)变量也是没问题的:

>>> x, y = y, x
>>> print x, y
3 1

事实上,这里所做的事情叫做序列解包可选代解包——.将多个值的序列解开,然后放到变量的序列中。更形象一点:

>>> values = 1, 2, 3
>>> values
(1, 2, 3)
>>> x, y, z = values
>>> x
1
>>> y
2
>>> z
3

当函数或者方法返回元组(或者其他序列或可迭代对象)时,这个特性尤其有用.假设需要获取(和删除)字典中任意的键-值对,可以使用 popitem 方法,这个方法将键-值作为元组返回.那么这个元组就可以直接赋值到两个变量中:

>>> scoundrel = {'name': 'Robin', 'girlfriend': 'Marion'}
>>> key, value = scoundrel.popitem()
>>> key
'girlfriend'
>>> value
'Marion'

它允许函数返回一个以上的值并且打包成元组,然后通过一个赋值语句很容易进行访问.所解包的序列中的元素数量必须和放置在赋值符号 = 左边的变量数量完全一致,否则 Python 会在赋值时引发异常:

>>> x, y, z = 1, 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: need more than 2 values to unpack
>>> x, y, z = 1, 2, 3, 4
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: too many values to unpack

注意:Python 3.0 中有另外一个解包的特性:可以像在函数的参数列表中一样使用星号运算符(参见第六章).

>>> a, b, *rest = 1, 2, 3, 4
>>> a
1
>>> b
2
>>> rest
[3, 4]
>>> 
>>> *rest, a, b = 1, 2, 3, 4
>>> a
3
>>> b
4
>>> rest
[1, 2]
>>> *rest, a, b = 1, 2 
>>> rest
[]

当使用星号的变量放置在第一个位置时,这样它就总会包含一个列表.右侧的赋值语句可以是可迭代对象.


链式赋值

链式赋值是将同一个值赋给多个变量的捷径.和并行赋值不同的是,这里只处理一个值:

x = y = somefunction()
or
x = somefunction()
y = x

增量赋值

增量赋值对于"+, -, *, /, %"等标准运算符都试用:

>>> x = 2
>>> x += 1
>>> x
3
>>> x *= 2
>>> x
6
>>> x * = 2
  File "<stdin>", line 1
    x * = 2
        ^
SyntaxError: invalid syntax

对于其他数据类型也适用(只要二元运算符本身适用于这些数据类型即可):

>>> fnord = 'foo'
>>> fnord += 'bar'
>>> fnord
'foobar'
>>> fnord *= 2
>>> fnord
'foobarfoobar'

语句块:缩排的乐趣

在代码前放置空格来缩进语句即可创建语句块.块的每行缩进应该是相同的量.在 Python 中,冒号(:)用来标识语句块的开始,块中每一个语句都是缩进的(缩进量相同).

条件和条件语句

这就是布尔变量的作用

真值(也叫做布尔值).下面的值在作为布尔表达式的时候,会被解释器看作假(false):

False    None    0    ""    ()    []    {}

换句话说,也就是标准值 False 和 None 、所有类型的数字 0(包括浮点型、长整型和其他类型)、空序列(比如空字符串、元组和列表)以及空的字典都为假.其他的一切都被解释为真,包括特殊值 True.

事实上,True 和 False 只不过是 1 和 0 的一种"华丽"的说法而已——看起来不同但作用相同.

>>> True
True
>>> true
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'true' is not defined
>>> False
False
>>> True == 1
True
>>> False == 0
True
>>> True + False + 32
33

布尔值 True 和 False 属于布尔类型,bool 函数可以用来(和 list, str 以及 tuple 一样)转换其他值.

>>> bool('I think')
True
>>> bool(42)
True
>>> bool('')
False
>>> bool(0)
False

因为所有值都可以用作布尔值,所以几乎不需要对它们进行显式转换(Python 会自动转换这些值).

注意:尽管 [] 和 “” 都是假值,但它们本身却不相等.

>>> bool([])
False
>>> bool("")
False
>>> [] == ""
False

条件执行和 if 语句

>>> name = raw_input('what is your name: ')
what is your name: wang Gumby
>>> if name.endswith('Gumby'):
...     print 'Hello, Mr.Gumby'
... 
Hello, Mr.Gumby

else 和 elif子句

>>> num = input('Enter a number: ')
Enter a number: 21
>>> if num > 0:
...     print 'the number is positive'
... elif num < 0:
...     print 'the number is negative'
... else:
...     print 'the number is zero'
... 
the number is positive

更复杂的条件

比较运算符

运算符"==, <, >, >=, <=, !=, is, is not, in, not in"都是比较运算符,'x is y'表示 x 和 y 是同一个对象,'x in y'表示 x 是 y 容器(例如,序列)的成员.

只有 x 和 y 是相同或者近似类型的对象时,比较才有意义(例如,两个整型数或者一个整型数和一个浮点型数进行比较).

Python 中比较运算和赋值运算一样是可以连接的——几个运算符可以连在一起使用,比如:0<age<100.

>>> age = 10
>>> 0<age<100
True

提示:比较对象的时候可以使用第二章中介绍的内建的 cmp 函数.

相等运算符

>>> "foo" == "foo"
True
>>> "foo" == "bar"
False

is: 同一性运算符

>>> x = y = [1, 2, 3]
>>> z = [1, 2, 3]
>>> x == y
True
>>> x ==z
True
>>> x is y
True
>>> x is z
False

变量 x 和 y 都被绑定到同一个列表上,而变量 z 被绑定在另外一个具有相同数值和顺序的列表上.它们的值可能相等,但是却不是同一个对象.

总结一下:使用'=='运算符来判定两个对象是否相等,使用 is 判定两者是否等同(同一个对象).

字符串和序列比较

字符串可以按照字母顺序排列进行比较:

>>> "alpha" < "beta"
True
>>> ord('a')
97
>>> chr(97)
'a'
>>> 'FnOrD'.lower() == 'fNord'.lower()
True
>>> 'FnOrD'.upper() == 'fNord'.upper()
True

其他的序列也可以用同样的方式进行比较,不过比较的不是字符而是元素的其他类型.

>>> [1, 2] < [2, 1]
True
>>> [2, [1, 4]] < [2, [1, 5]]
True

布尔运算符

布尔运算符:and, or, not.在 Python 中会使用短路逻辑.

>>> number = 3
>>> if number <= 10 and number >= 1:
...     print 'Great'
... else:
...     print 'Wrong'
... 
Great
>>> 
>>> number = 3
>>> if 1 <= number <= 10:
...     print 'Great'
... else:
...     print 'Wrong'
... 
Great

name = raw_input('Please enter your name: ') or '<unknown>'

>>> 1 if 2 else 3
1
>>> 1 if False else 3
3
>>> 1 if True else 3
1

循环

while 循环

x = 1
while x <= 100:
    print x
    x += 1

name = ''
while not name:        # while not name or name.isspace(): while not name.strip():
    name = raw_input('Please input your name: ')
print 'Hello. %s!' % name

for 循环

要为一个集合(序列和其他可迭代对象)的每个元素都执行一个代码块,可以使用 for 循环.

numbers = [0, 1, 2, 3, 4, 5]
for num in numbers:
    print num

因为迭代(循环)某范围的数字是很常见的,所以有个内建的范围函数供使用:

>>> range(0, 10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> range(2, 10)
[2, 3, 4, 5, 6, 7, 8, 9]
>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

range 函数的工作方式类似于分片.它包含下限但不包含上限,即:前闭后开.如果希望下限为 0,可以只提供上限.

提示:如果能使用 for 循环,就尽量不用 while 循环.

xrange 函数的循环行为类似于 range 函数,区别在于 range 函数一次创建整个序列,而 xrange 一次只创建一个数.当需要迭代一个巨大的序列时 xrange 会更高效,不过一般情况下不需要过多关注它.

>>> xrange(10)
xrange(10)

循环遍历字典

一个简单的 for 语句就能循环字典的所有键,就像处理序列一样:

>>> d = {'x': 1, 'y': 2, 'z': 3} 
>>> for key in d:
...     print key, 'corresponds to', d[key]
... 
y corresponds to 2
x corresponds to 1
z corresponds to 3

>>> d.keys()
['y', 'x', 'z']
>>> d.values()
[2, 1, 3]

d.items 方法会将键-值对作为元组返回,for 循环的一大好处就是可以循环中使用序列解包:

>>> for key, value in d.items():
...     print key, 'corresponds to', value
... 
y corresponds to 2
x corresponds to 1
z corresponds to 3

注意:字典元素的顺序通常是没有定义的.迭代的时候,字典中的键和值都能保证被处理,但是处理顺序不确定.如果顺序很重要的话,可以将键值保存在单独的列表中,在迭代前进行排序.


一些迭代工具

并行迭代

>>> names = ['anne', 'beth', 'george', 'damon']
>>> ages = [12, 45, 32, 102]
>>> for i in range(len(names)):
...     print names[i], 'is', ages[i], 'years old'
... 
anne is 12 years old
beth is 45 years old
george is 32 years old
damon is 102 years old

内建的 zip 函数可以把两个序列"压缩"在一起,然后返回一个元组的列表:

>>> zip(names, ages)
[('anne', 12), ('beth', 45), ('george', 32), ('damon', 102)]
>>> 
>>> for name, age in zip(names, ages):
...     print name, 'is', age, 'years old'
... 
anne is 12 years old
beth is 45 years old
george is 32 years old
damon is 102 years old

zip 函数也可以作用于任意多的序列.并且可以应付不等长的序列,当最短的序列"用完"的时候就会停止:

>>> zip(range(5), xrange(10000000))
[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]

在上面的代码中,xrange 只计算前 5 个数,如果替换为 range,则要计算所有的值,这需要花费很长时间.

编号迭代

有些时候想要迭代序列中的对象,同时还要获取当前对象的索引.例如,在一个字符串列表中替换所有包含'xxx'的子字符串.实现方法有很多:

# 方法1
for string in strings:
    if 'xxx' in string:
        index = strings.index(string)
        strings[index] = '[censored]'

# 方法2
index = 0
for string in strings:
    if 'xxx' in string:
        strings[index] = '[censored]'
    index += 1

另一种方法是使用内建的 enumerate 函数:

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

翻转和排序迭代

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'
>>> reversed('Hello, world!')
<reversed object at 0x7f6729129910>

注意,虽然 sorted 方法返回列表,reversed 方法却返回一个更加不可思议的可迭代对象.我们可以在 for 循环以及 join 方法中使用,而不会有任何问题.不过不能直接对它使用索引、分片以及调用 list 方法,如果希望进行上述处理,那么可以使用 list 类型转换返回的对象.

跳出循环

break:结束(跳出)循环.

continue:它会结束当前的迭代,"跳"到下一轮循环的开始.即:"跳过剩余的循环体,但是不结束循环".

循环中的 else 子句

在循环中增加一个 else 子句——它仅在没有调用 break 时执行.

>>> from math import sqrt
>>> for n in range(99, 8, -1):
...     root =  sqrt(n)
...     if root == int(root):
...             print n
...             break
... else:
...     print "Did not find it!"
... 
81

>>> from math import sqrt
>>> for n in range(99, 81, -1):
...     root =  sqrt(n)
...     if root == int(root):
...             print n
...             break
... else:
...     print "Did not find it!"
... 
Did not find it!

列表推导式——轻量级循环

列表推导式是利用其他列表创建新的列表的一种方法.它的工作方式类似于 for 循环:

>>> [x * x for x in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> [x * x for x in range(10) if x % 3 == 0]
[0, 9, 36, 81]

>>> girls = ['alice', 'bernice', 'clarice']
>>> boys = ['charis', 'arnold', 'bob']
>>> [b + '+' + g for b in boys for g in girls if b[0] == g[0]]
['charis+clarice', 'arnold+alice', 'bob+bernice']

更优方案:
>>> 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]]]
['chris+clarice', 'arnold+alice', 'bob+bernice']
>>> 

三人行

走马观花的看下另外 3 个语句:pass,del 和 exec

什么都没发生

有些时候,程序什么事情都不用做.它可以在代码中做占位符使用.

if name == 'Ralph':
    pass

使用 del 删除

>>> scoundrel = {'age': 42, 'first name': 'Robin', 'last name': 'of Locksley'}
>>> robin = scoundrel
>>> scoundrel
{'last name': 'of Locksley', 'first name': 'Robin', 'age': 42}
>>> robin
{'last name': 'of Locksley', 'first name': 'Robin', 'age': 42}
>>> scoundrel = None
>>> robin
{'last name': 'of Locksley', 'first name': 'Robin', 'age': 42}
>>> robin = None

当 scoundrel 和 robin 都设为 None 时,字典就'漂'在内存里面了,不能获取和使用它,所以 Python 解释器使用垃圾收集删除了那个字典.

另一种方法是使用 del 语句,它不仅会移除一个对象的引用,也会移除那个名字本身.

>>> x = 1
>>> del x
>>> x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined

>>> x = ["hello", "world"]
>>> y = x 
>>> y[1] = "python"
>>> x
['hello', 'python']
>>> 
>>> del x
>>> y
['hello', 'python']
>>> x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined
>>> 

del 删除的只是名称,而不是列表本身(值).事实上,在 Python 中是没有办法删除值的.

使用 exec 和 eval 执行和求值字符串

执行存储在字符串中的 Python 代码

exec

执行一个字符串语句:

>>> exec "print 'hello world'"
hello world

最好给它提供一个命名空间(字典):

>>> from math import sqrt
>>> exec 'sqrt = 1'
>>> sqrt(4)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: 'int' object is not callable
>>> sqrt
1

可以通过增加 in 来实现,其中的 就是起到放置代码字符串命名空间作用的字典.

>>> from math import sqrt
>>> scope = {}
>>> exec 'sqrt = 1' in scope
>>> sqrt(4)
2.0
>>> sqrt
<built-in function sqrt>
>>> scope['sqrt']
1

注意:如果要将 scope 打印出来的话,会单刀其中包含很多东西,因为内建的 builtins 字典自动包含所有的内建函数和值:

>>> len(scope)
2
>>> scope.keys()
['__builtins__', 'sqrt']

eval

用于"求值",是类似于 exec 的内建函数.exec 语句会执行一系列 Python 语句,而 eval 会计算 Python 表达式(以字符串的形式书写),并返回结果值,(exec 语句并不返回任何对象,因为它本身就是语句).

>>> eval(raw_input("Enter an arithmetic expression: "))
Enter an arithmetic expression: 2 * 3 + 6
12

注意:表达式 eval(raw_input(…)) 事实上等同于 input(…).在 Python 3.0 中,raw_input 被重命名为 input.

可以给 eval 提供两个命名空间,一个全局的一个局部的.全局的必须是字典,局部的可以是任何形式的映射.

给 exec 或者 eval 语句提供命名空间时,还可以在真正使用命名空间前放置一些值进去:

>>> scope = {}
>>> scope['x'] = 2
>>> scope['y'] = 3
>>> eval('x * y', scope)
6

同理,exec 或者 eval 调用的作用域也能在另外一个上面使用:

>>> scope = {}
>>> exec 'x = 2' in scope
>>> eval('x * x', scope)
4