第五章:条件、循环和其他语句
这一章会看到列表推导式(list comprehension)如何扮演循环和条件语句的角色——尽管它本身是表达式。最后介绍pass
,del
,exec
语句的用法。
print和import的更多信息、
可以打印多个表达式,只要将它们用逗号隔开。
>>>print 'age',24
age 24
可以看到,每个参数之间都插入了一个空格符。python3中print
不再是语句——而是函数。
赋值魔法
就算是不起眼的赋值语句也有一些特殊技巧。
序列解包
多个赋值操作可以同时进行:
>>>x,y,z=1,2,3
>>>print x,y,z
1 2 3
很有用吧?用它交换两个(或更多个)变量也是没问题的:
>>>x,y,z=y,z,x
>>>print x,y,z
2 3 1
事实上这里所做的事情就是叫做序列解包(sequence unpacking)或递归解包——将多个值的序列解开,然后放到变量的序列中。更形象一点的表示就是:
>>>values=1,2,3
>>>values
(1,2,3)
>>>x,y,z = values
>>>x
1
所有序列解包的序列中的元素数量必须和放在复制符号=左边的变量数量完全一致。
链式赋值
链式赋值(chained assignment)是将同一个值赋给多个变量的捷径。
x = y = somefunction()
和下面的语句效果是一样的:
y = somefunction()
x = y
注:上面的语句和下面的语句不一定等价:
x = somefunction()
y = somefunction()
有关链式赋值更多信息,参加本章“同一性运算符”
增量赋值
将表达式运算符放置在赋值运算符=的左边,写成x+=1
。这种写法叫做增量赋值(augmented assignment),对于*、/、%等标准运算符都适用:
>>>x=2
>>>x+=1
>>>x*=2
>>>x
6
对于其他数据类型的也适用(只要二元运算发本身是用这些数据类型即可):
>>>fonrd='foo'
>>>fonrd+='ba'
>>>fonrd*=2
>>>fonrd
'foobarfoobar'
语句块:缩排的乐趣
标准且推荐的方式是只用空格((⊙o⊙)…我一直喜欢用tab)。
条件和条件语句
布尔变量的作用
真值(也叫作布尔值,这个名字是根据在真值上做过大量研究的George Boole命名的)。
下面的值作为布尔表达式的时候,会被解释器看作假(False):
False
None
0 包括所有类型的数字0
"" 注意:引号(或者单引号)之间不能有空格,如果有空格,那么就不是
False
了![]
{}
其他的一切都被解释为真值,包括特殊值True。(可能有待商榷)
明白了吗?也就是说python中的所有值都能被解释为真值
标准的布尔值为0和1.事实上,True和False只不过是1和0的另一种“华丽”的说法而已。
>>>True
True
>>>False
False
>>>True==1
True
>>>False==0
True
>>>False+True+24
24
注:False和True是要首字母大写的,false和true只会被解释器认为是变量名!!!
布尔值True和Flase属于布尔类型,bool
函数可以用来(和list、str、tuple一样)转换其他值。
>>>bool('my name is michael'
True
>>>bool('')
False
条件执行和if语句
num=int(raw_input('enter a number:')) #或者num=input('enter a number:') 二者区别,之前的笔记中也写过了。
if num>0:
print 'the number is positive'
elif num<0:
print 'the number is negative'
else:
print 'the num is 0'
更复杂的条件
1.比较运算符
表达式 | 描述 |
---|---|
x==y | x等于y |
x<y | x小于y |
x !=y | x不等于y |
x is y | x和y是同一个对象 |
x is not y | x和y不是同一个对象 |
x in y | x是y容器(例如,序列)的成员 |
x not in y | x不是y容器(例如,序列)的成员 |
注:如果偶然遇见x<>y
这样的表达式,它的意思就是x!=y
,但是不建议使用<>
运算符。
python中比较运算符和赋值运算一样是可以连接的——几个运算符可以连接在一起使用,比如:0<age<100
。
2. 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和z相等,却不等同。因为is运算符是判定同一性的而不是相等性的。
总结一下:==
运算符是判定两个对象是否相等,is
判定两者是否等同(同一个对象)。
注:避免将is
运算符用于比较类似数值和字符串这类不可变值。由于python内部操作这些对象的方式不同,使用is
运算符的结果是不可预测的!
3. 字符串和序列比较
>>>[1,2]<[2,1]
True
不是很明白比较结果为何是这样的。
4. 布尔运算符
and
or
not
5. 断言
语句中使用的关键字:assert
。
一般说来,捏可以要求某些条件必须为真(例如,在检查函数参数的属性时或者作为初期测试和调试过程的辅助条件)。
>>>age=10
>>>assert 0<age<100
>>>age=-1
>>>assert 0<age<100,'the age must be positive' #条件后可以添加字符串,用来解释断言
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
<ipython-input-12-51358c1c723f> in <module>()
1 age=-1
----> 2 assert 0<age<100,'the age must be positive'
AssertionError: the age must be positive
循环
while循环
name=''
while not name:
name=raw_input('enter a name:')
print 'hello %s'%name
如果直接输入一个空格的话,那么name就非空了,所以,不会判定为假!
修改:只需while not name or name.isspace()
。
for循环
while语句很灵活,它可以用来在任何条件为真的情况下重复执行一个代码块。一般情况下这样就够用了,但是有些时候还得量体裁衣。比如要为一个集合(序列和其他可迭代对象)的每个元素都执行一个代码块:
可迭代对象是指可以按次序迭代的对象(也就是用于for循环中的)。
因为迭代(循环的另外一种说法)某些范围的数字时很常见的,所有有个内建的范围函数共使用:
>>>range(0,10) #当要求0为下限时,也可以range(10)
[0,1,2,3,4,5,6,7,8,9]
还是左闭右开的范围。
提示:如果能使用for循环,就尽量不用while循环。
xrange
函数的循环行为类似range
函数,区别在于range
函数一次创建整个序列,而xrange
函数一次只创建一个数。但需要跌在一个巨大的序列时,xrange
会更高效!不过一般情下不需要
循环遍历字典元素
d={'x':1.'y':2,'z':3}
for key in d:
print key,'cooreponds to',d[key]
字典无序,如果顺序很重要的话,可以将键值保存在单独的列表中,例如在迭代前进行排序。
一些迭代的工具
在Python中迭代序列(或者其他可迭代对象)时,有一些函数非常好用。有些函数位于itertools
模块中,还有一些python的内建函数也非常方便。
1. 并行迭代-zip
函数
程序可以同时迭代两个序列。比如有下面两个列表:
names = ['michael','hh','qq']
ages = [24,23,55]
#如果想要打印名字和对应的年龄,可以这么做
for i in range(len(names)):
print names[i],'is',ages[i],'years old'
而内建的zip
函数就可以进行并行迭代,可以把两个序列“压缩”在一起,然后返回一个元祖的列表:
>>>zip(names,ages)
[('michael', 24), ('hh', 23), ('qq', 55)]
现在我可以在循环中解包元祖:
for name,age in zip(names,ages):
print name,'is',age,'years old'
zip
函数也可以作用于任意多的序列。关于它很重要的一点是zip
可以处理不等长的序列,当最短的序列“用完”的时候就会停止:
>>>zip(range(5),xrange(100000000))
[(0,0),(1,1,),(2,2,),(3,3,),(4,4,)]
在此处,就不推荐range
代替xrange
,因为我们只需要前5个数字,但是range
会计算所有的数字,这要花费很长的时间。
2. 按索引迭代-enumerate
函数。
有些时候想要迭代访问序列中的对象,同时还要获得当前对象的索引。
例如,在一个字符串列表总替换所有包含'aaa'的子字符串。
strings=['aaa','bbb','ccc','aaa']
for index,string in enumerate(strings):
if 'aaa' in string:
strings[index]='xxx'
>>>strings
['xxx', 'bbb', 'ccc', 'xxx']
这个函数可以提供索引的地方迭代索引-值对。
3. 翻转和排序迭代。
让我们看看两个有用的函数:reversed
和sorted
。它们同列表的reverse
和sort
(sorted和sort使用同样的参数)方法类似。但作用于任何序列或可迭代对象上,不是原地修改对象,而是返回翻转或者排序后的版本:
>>>a=[2,1,4,3]
>>>sorted(a)
[1,2,3,4]
>>>a
[2,1,4,3]
看见了吧,sorted不是原地修改对象!!!它是又返回值的,返回排序或者翻转后的版本!
跳出循环
break
continue
跳出剩余的循环体,但不结束循环
尽管continue
语句非常有用,它却不是最本质的。应该习惯使用break
语句,因为在while True语句中会经常用到它。
while Ture/break习语
哑值(dummy value)就是工作没有尽善尽美的标志。
循环中的else语句
当在循环内使用break
语句时,通常是因为“找到”了某物或者因为某事“发生了”。
在循环中增加一个else
子句--它仅在没有调用break
时执行。
from math import sqrt
for n in range(99,0,-1):
root=sqrt(n)
if root ==int(root):
print n
break
else:
print "Didn't find it"
列表推导式--轻量级循环
列表推导式(list comprehension)是利用其它列表创建新列表的一种方法。它的工作方式类似于for
循环,也很简单。
>>>[x*x for x in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
可以通过增加if
部分添加到列表推导式中,找出能被3整除的数:
>>>[x*x for x in range(10) if x%3==0]
[0,9,36,81]
也可以增加更多for
语句的部分:
>>>[(x,y) for x in range(2) for y in range(3)]
[(0,0),(0,1),(0,2),(1,0),(1,1),(1,2)]
三人行
作为本章的结束,让我们走马观花地看一下另外3个语句:pass
、del
、exec
。
什么都没发生
有的时候,程序什么事情都不用做。这时候就该让pass
出场了:
if name =='michael':
print hello name
elif name =='qq':
pass #测试的时候,如果这里还没想好写什么可以用pass作占位符!因为python中空代码块是非法的,这里缺少一个语句块是不行的。
elif name == 'bill':
print 'success'
使用del删除
python会删除那些不再使用的对象。
>>>x1={'name':'michael','age':24}
>>>x2=x1
>>>x1=None
>>>x2
{'name':'michael','age':24}
>>>x2=None
当x1
和x2
同时设置为None
时,字典就“漂”在内存里边了(我觉得书中的这个描述特别形象啊!),没有任何名字绑定到它上面。没有办法获取和使用它,所以,python解释器(以其无穷的智慧)直接删除了那个字典(这称为垃圾收集)。
注意,也可以使用None
之外的其他值。字典同样会“消失不见”。
另外一个方法就是使用del
语句(我们在第二章和第四章里边用来删除序列和字典元素的语句),它不仅会移除一个对象的引用,也会移除那个名字本身!!!
什么叫移除名字本身?
栗子1:
>>>x = 1
>>>del x
>>>x
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-55-401b30e3b8b5> in <module>()
----> 1 x
NameError: name 'x' is not defined
栗子2:
>>>x=1
>>>x=None
>>>x
栗子1和栗子2的结果可以发现,栗子2中再输入x
时没有报错,说明名字本身没有被删除。而栗子1名字本身被删除了,也就是x
删除了。
但是,del
也是没有把和名字绑定的值从内存中删除的!在python中是没有办法彻底删除值的(不需要过多考虑删除值的问题,因为在某个值不再使用的时候,python解释器会负责内存的回收)。
使用exec和eval执行和求值字符串
有些时候需要动态地创建python代码,然后将其作为语句执行或作为表达式计算,这可能近似于“黑暗魔法”——在此之前,一定要慎之又慎,仔细考虑。
在此节中会学到如何执行存储在字符串中的python代码。这样做会有很严重的潜在安全漏洞。(p85)
exec
执行一个字符串的语句是exec
:
>>>exec 'print "hello michael"'
hello michael
注:在python3中,exec是一个函数而不是一个语句。
但是,使用简单形式的exec
语句绝不是好事。很多情况可以给它提供命名空间——可以放置变量的地方。你想这样做,从而使代码不会干扰命名空间。
eval
eval
(用于“求值”)是类似于exec
的内建函数。eval
会计算python表达式(以字符串形式书写),并且返回结果值。(exec
语句并不返回任何对象,因为它本身就是语句)。
>>>eval(raw_input("enter an expression:"))
enter an expression:1+2
3
eval
也可以使用命名空间。
本章小结
总体来说,这章的内容算是复习吧,感想就是:
一定要关注函数、方法它们的返回值!