Python回顾与整理6:条件和循环

0.说明python


        说起的内容为:if、while、for及与他们相搭配的else、elif、break、continue和pass语句。
算法




1.if语句express


  • 语法缓存

if expression:
        expr_true_suite

        其中对于expression部分可使用逻辑链接词an、or和not来实现多重判断条件。数据结构

  • 单一语句的代码块ide

        即若是只有一个语句执行时,能够写成下面这样:
函数

if True: print 'OK'

        但仍是建议写在不一样的行。
工具




2.else语句性能


  • 语法测试

if expression:
        expr_true_suite
else:
        expr_false_suite
  • 避免“悬挂else”

        像C语言这种使用括号来分隔代码块的语言会出现这种状况(即在有多个if语句蛙,else语句不知道是属于哪个if语句的),但因为Python是强制使用缩进来使代码对齐的,所以不可能会出现这种问题。




3.elif语句


  • 语法

if expression1:
        expr1_true_suite
elif expression2:
        expr2_true_suite
elif expression3:
        expr3_true_suite
        ……
elif expressionN:
        expr1_true_suite
else:
        none_of_the_above_suite

        能够有任意数量的elif语句,但只能有一个else语句。

  • switch/case语句的替代品

        在C语言中有switch/case语句用于在多个选项中进行选择,Python虽然没有switch/case语句,但却有更好的解决方案来实现一样的功能:

#方案一:大量的if-elif语句
if user.cmd == 'create':
        action = 'create item'
elif user.cmd == 'delete':
        action = 'delete item'        
elif user.cmd == 'update':
        action = 'update item'        
        
#方案二:用序列和成员关系操做符
if user.cmd in ('create', 'delete', 'update'):
        action = '%s item' % user.cmd
else:
        action = 'invalid choice... try again!'      
        
#方案三:使用字典
msgs = {'create', 'create item',
               'delete': 'delete item',
               'update': 'update item'}
default = 'invalid choice... try again!'
action = msgs.get(user.cmd, default)
#使用映射对象(好比字典)的一个最大好处就是它的搜索操做比相似if-elif-else语句或是for循环这样的序列查询要快不少

        能够看到,对于实现一样的功能,Python中的解决方案更增强大和简洁。




4.条件表达式(三元操做符)


  • 语法:X if C else Y

        使用以下:

>>> x, y =4, 3
>>> smaller = x if x < y else y
>>> smaller
3




5.while语句


  • 语法

while expression:
        suite_to_repeat
  • 计数循环

count = 0
while (count < 9):
        print 'the index is: ',count
        count += 1
  • 无限循环

while True:
        suite_to_repeat

        无限循环主要是用在Server/Client模式中Server端的设计,用以等待来自客户端的链接,固然若是有必要的话也是能够经过break语句来结束循环的。




6.for语句


        for语句提供了Python中最强大的循环结构,它能够遍历序列成员,能够用在列表解析和生成器表达式中,它会自动地调用迭代器的next()方法,捕获StopIteration异常并结束循环(这一切都是在内部发生的)。


(1)语法

for iter_var in iterable:
        suite_to_repeat

        每次循环,iter_var抚迭代变量被设置为可迭代对象iterable(序列、迭代器或其余支持迭代的对象)的当前元素,提供给suite_to_repeat语句使用。


(2)用于序列类型

        主要是下面用于序列迭代的三种方法:

  • 经过序列项迭代

>>> nameList = ['xpleaf', 'clyyh', 'cl']
>>> for name in nameList:
...   print name
... 
xpleaf
clyyh
cl
  • 经过序列索引迭代

>>> nameList = ['xpleaf', 'clyyh', 'cl']
>>> for nameIndex in range(len(nameList)):
...   print nameList[nameIndex]
... 
xpleaf
clyyh
cl

        显然会比第一种方法慢不少。

  • 使用项和索引迭代

>>> nameList = ['xpleaf', 'clyyh', 'cl']
>>> for nameIndex, name in enumerate(nameList):
...   print nameIndex, name
... 
0 xpleaf
1 clyyh
2 cl


(3)用于迭代器类型

        for语句用于迭代器时,会自动帮咱们处理不少问题(在内部调用next()并处理StopIteration异常),不过须要注意的是,迭代器并不表明循环条目的集合。


(4)range()内建函数

        range()能够用来生成一个数字顺序列表,从而可使用for来进行迭代,它主要有如下两种语法:

  • 完整语法

        以下:

range(start, end, step=1)

        即默认步长为1,举例以下:

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

        可是步长不能为0,不然会出错:

>>> range(1, 10, 0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: range() step argument must not be zero
  • 简单语法

range(end)    #即默认start=1
range(start, end)    #其实就是默认步长为1的状况

        举例以下:

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


(5)xrange()内建函数

        当调用range()时,会直接生成一个列表并写入内存中,但当生成的列表很大时,使用range()就不适合了,这时能够考虑能够xrange(),它返回一个可迭代的对象,而并不会生成一个完整的列表,所以它只被用于for循环中,不然是没有意义的:

>>> xrange(10)
xrange(10)
>>> type(xrange(10))
<type 'xrange'>
>>> for num in xrange(10):
...   print num,
... 
0 1 2 3 4 5 6 7 8 9


(6)与序列相关的内建函数

        主要是:sorted()reversed()enumerate()zip()

        之因此说是与序列相关,是由于其中两个函数(sorted()和zip())返回一个序列(列表),而另外两个函数(reversed()和enumerate())返回迭代器,举例以下:

  • sorted():返回一个列表

>>> nameTuple = ('xpleaf', 'cl')
>>> years = (1994, 1995)
>>> sorted(nameTuple)
['cl', 'xpleaf']
>>> for name in sorted(nameTuple):
...   print name,
... 
cl xpleaf
  • reversed():返回一个迭代器

>>> reversed(nameTuple)
<reversed object at 0x7f2fa02df390>
>>> for name in reversed(nameTuple):
...   print name,
... 
cl xpleaf
  • enumerate():返回一个迭代器

>>> enumerate(nameTuple)
<enumerate object at 0x7f2f9e1c1d20>
>>> for nameIndex, name in enumerate(nameTuple):
...   print nameIndex, name
... 
0 xpleaf
1 cl
  • zip():返回一个列表

>>> zip(nameTuple, years)
[('xpleaf', 1994), ('cl', 1995)]
>>> for name, year in zip(nameTuple, years):
...   print name, year
... 
xpleaf 1994
cl 1995




7.break语句

        

        break能够结束当前循环而后跳转到下一条语句(若是存在的话),可用于while和for两各循环中:

>>> count = 0
>>> while True:
...   if count>10:
...     print 'OK'
...     break
...   count += 1
... 
OK




8.continue语句


        continue语句的做用:当遇到ontinue语句时,程序会终止当前循环,并忽略剩余的语句,而后回到循环的顶端。在开始一次迭代前,若是是条件循环,咱们将验证条件表达式;若是是迭代循环,咱们将验证是否还有元素能够迭代。只有在验证成功的状况下,才会开始下一次迭代。

        对在Python中,while循环是条件性的,for循环是迭代的,这就能够跟上面的两种状况对应起来,可简单举例以下:

>>> count = 0
>>> while count<10:
...   count += 1
...   if count != 6:
...     continue
...   print count
... 
6
>>>
>>> for count in range(10):
...   if count != 6:
...     continue
...   print count
... 
6




9.pass语句


        主要是下面几个方面会用到pass语句:

  • 有些地方在语法上要求有代码

  • 标记后来要完成的代码(好比先定义个类或函数,但什么也不作)

  • 在异常处理中对于影响不大的异常能够直接pass




10.再谈else语句


        在Python中,除了在条件语句中使用else外,还能够在while和for循环中使用else语句。当在循环中使用时,else子句只在循环完成后执行,即若是此时循环中存在break语句就会跳过该else语句。

        举例以下:寻找一个数的最大约数

#!/usr/bin/env python


def showMaxFactor(num):
    count = num / 2  #从一半开始计数,这样就能够检查这个数是否能够被2整除,若是能够,那就找到了最大的约数
    while count > 1:
        if num % count == 0:
            print 'largest factor of %d is %d' % (num, count)
            break
        count -= 1
    else:
        print num, 'is prime'

if __name__ == '__main__':
    showMaxFactor(10)
    showMaxFactor(31)

        这是是一个很是好的特性,能够考虑一下,若是没有该特性,要实现上面的功能,在逻辑上就确定没有那么清晰了。




11.迭代器和iter()函数


(1)什么是迭代器

        所谓迭代器,其实就是一个有next()方法的Python对象,由于它为类序列对象提供了一个类序列的接口,因此可使用for循环进行迭代(其实for循环在进行序列迭代时是先把序列转换为迭代器再进行迭代的,这是序列自己的功能,之因此会建立迭代器,是由于在序列中实现了__iter__()方法),因此只要是表现出序列行为的对象(可迭代对象),均可以使用迭代器进行迭代。


(2)为何要迭代器

        主要是理解下面几点:

  • 对列表迭代带来了性能上的加强(节省内存空间)

  • 迭代非序列集合(例如映射和文件)时,能够建立更简洁的代码


(3)如何迭代

        迭代器就是一个有next()方法的对象,而不是经过索引来计数。当你或是一个循环机制(好比for)须要下一个项时,调用迭代器的next()方法就能够得到它,当所有项获取后会有一个StopIteration异常,表示迭代已经完成。前面的reversed()和enumerate()函数返回的就是一个迭代器。

        固然迭代器也是有一些限制的,即不能向后移动、不能复制一个迭代器,当须要从新迭代同个对象时,就须要去建立另外一个迭代对象(不过迭代器自己有seek()方法能够帮咱们完成这些事情)。


(4)使用迭代器

  • 序列

        以下:

>>> nameTuple = ('xpleaf', 'clyyh', 'cl')
>>> i = iter(myTuple)
>>> i.next()
'xpleaf'
>>> i.next()
'clyyh'
>>> i.next()
'cl'
>>> i.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

        实际上,序列会自动地建立它们本身的迭代器,因此一个for循环:

>>> nameTuple = ('xpleaf', 'clyyh', 'cl')
>>> for name in nameTuple:
...   print name,
... 
xpleaf clyyh cl

        其实是这样工做的:

>>> fetch = iter(nameTuple)
>>> while True:
...   try:
...     i = fetch.next()
...   except StopIteration:
...     break
...   print i,
... 
xpleaf clyyh cl

        即会先建立该序列类型的一个迭代器,而后调用迭代器的next()方法进行迭代。由于序列自己只是为咱们建立了迭代器而已,因此for循环语句为咱们完成了下面两件事:

(a)自动调用迭代器的next()方法

(b)监视StopIteration异常

  • 字典

        字典的迭代器会遍历它的键,语句for eachKey in dict.keys()就能够缩写为for eachKey in dict,以下:

>>> myDict = {'name': 'xpleaf', 'loving': 'cl'}
>>> for key in myDict.keys():
...   print key,
... 
name loving
>>>
>>> for key in myDict:
...   print key,
... 
name loving

        而根据前面的理论知识,能够考虑它们是这样工做的(实际上可能不是):

>>> fetch = iter(myDict)
>>> while True:
...   try:
...     i = fetch.next()
...   except StopIteration:
...     break
...   print i,
... 
name loving
  • 文件

        文件对象生成的迭代器会自动调用readline()方法,这样循环就能够访问本文文件的全部行,便可以使用for eachLine in file来替换for eachLine in file.readlines,以下:

>>> f = open('welcome.txt', 'r')
>>> lines = f.readlines()
>>> lines
['Welcome to GDUT!\n', "Haha, I'm xpleaf, and I love Python programming!\n", 'Also, I love cl.\n']
>>> for line in lines:
...   print line,
... 
Welcome to GDUT!
Haha, I'm xpleaf, and I love Python programming!
Also, I love cl.
>>> 
>>> f = open('welcome.txt', 'r')
>>> for line in f:
...   print line,
... 
Welcome to GDUT!
Haha, I'm xpleaf, and I love Python programming!
Also, I love cl.

        根据前面的理论知识,实际上它们是这样工做的:

>>> f = open('welcome.txt', 'r')
>>> fetch = iter(f.xreadlines())
>>> while True:
...   try:
...     i = fetch.next()
...   except StopIteration:
...     break
...   print i,
... 
Welcome to GDUT!
Haha, I'm xpleaf, and I love Python programming!
Also, I love cl.
# 猜想使用的是xreadlines()而不是readlines(),是由于readlines()返回的是一个完整的列表,这会占用
# 大量的内存空间,显然是不正确的,而xreadlines()返回的是一个迭代器。

        固然,由于文件对象自己就是一个迭代器,能够直接调用它的next()方法来实现上面的功能:

>>> f = open('welcome.txt', 'r')
>>> while True:
...   try:
...     i = f.next()
...   except StopIteration:
...     break
...   print i,
... 
Welcome to GDUT!
Haha, I'm xpleaf, and I love Python programming!
Also, I love cl.


(5)可变对象和迭代器

        不建议在迭代可变对象的时候修改它们,好比一个可变的序列列表,由于一个序列的迭代器只是记录你当前到达第几个元素,因此若是在迭代时改变了元素(或删除了元素),更新会当即反映到所迭代的条目上(序列上),以下:

>>> nameList = ['xpleaf', 'clyyh', 'cl']
>>> for name in nameList:
...   if name == 'xpleaf':
...     nameList.remove(name)
...   print name,
... 
xpleaf cl
>>> nameList
['clyyh', 'cl']
# 显然一不注意,就会出现问题,由于更新是实时的,而迭代器只是记录了当前位置,因此会看到有些问题,clyyh并无输出
>>> nameList = ['xpleaf', 'clyyh', 'cl']
>>> for name in nameList:
...   if name == 'xpleaf':
...     nameList[nameList.index(name)] = 'yonghaoye'
...     print nameList
... 
['yonghaoye', 'clyyh', 'cl']

        须要注意的是,在迭代字典时,若是改变这个字典,就会出现问题:

>>> myDict = {'name': 'xpleaf', 'loving': 'cl'}
>>> for key in myDict:
...   print key, myDict[key]
...   if key == 'name':
...     myDict[key] = 'yonghaoye'
... 
name xpleaf
loving cl
# 只是改变值是没有问题的
>>> myDict
{'name': 'yonghaoye', 'loving': 'cl'}
>>> 
>>> for key in myDict:
...   print key, myDict[key]
...   if key == 'name':
...     del myDict[key]
... 
name yonghaoye
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: dictionary changed size during iteration
# 可是若是删除字典中的一个键值对就会出现问题

        前面在迭代列表时删除列表的元素也是能够的,可是在迭代字典时删除字典的键值对就会有问题,注意二者的迭代对象是不同的,前者列表迭代器,然后者是字典键迭代器,考虑到字典其自己特殊的存储方式,这也是能够理解的,固然要深刻研究的话,想必须要查看Python的源代码了。

        因此在须要迭代时去删除字典的键值对,可使用字典的keys()方法,由于keys()返回一个独立于字典的列表。


(6)如何建立迭代器

        对一个对象调用iter()就能够获得它的迭代器,语法以下:

  • iter(obj)

  • iter(func, sentinel)

        当传递一个参数给iter(),它会检查你传递的是否是一个序列,若是是,那么很简单:根据索引从0一直迭代到序列结束(可是字典则不同)。固然也可使用类的方法,一个实现了__iter__和next()方法的类能够做为迭代器使用。

        若是是传递两个参数给iter(),它会重复地调用func,直到迭代器的下个值等于sentinel。




12.列表解析


        列表解析(List comprehensions或list comps)能够用来动态地建立列表,它主要有如下两种语法形式:

  • 简单语法

[expr for iter_var in iterable]

        举例以下:

>>> [x ** 2 for x in range(6)]
[0, 1, 4, 9, 16, 25]

        若是不用列表解析建立相同的列表,就会有些麻烦:

>>> map(lambda x: x ** 2, range(6))
[0, 1, 4, 9, 16, 25]
# map()对全部的列表成员应用一个操做,lambda用于快速地建立只有一行的函数对象
  • 扩展语法

[expr for iter_var in iterable if cond_expr]

        举例以下:

# 挑选序列中的奇数
>>> seq = [11, 10, 9, 9, 10, 10, 9, 8, 23, 9, 7, 18, 12, 11, 12]
>>> [x for x in seq if x % 2]
[11, 9, 9, 9, 23, 9, 7, 11]
# 注意这里使用的方法`if x % 2`,这是判断厅偶数的一个很是好的方法

        若是不用列表解析,则也会挺麻烦:

>>> filter(lambda x:x % 2, seq)
[11, 9, 9, 9, 23, 9, 7, 11]
# filter()基于一个条件表达式过滤列表成员

        显然会看到使用列表解析就会方便不少,也有下面的相关例子:

  • 矩阵样例

        好比要迭代一个3行5列的矩阵,可使用下面的算法:

>>> [(x+1, y+1) for x in range(3) for y in range(5)]
[(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (2, 1), (2, 2), (2, 3), (2, 4), (2, 5), (3, 1), (3, 2), (3, 3), (3, 4), (3, 5)]
  • 磁盘文件样例

        计算文件大小的,算法以下:

sum([len(word) for line in f for word in line.split()])

        上面两个例子其实都是嵌套循环,从中也能够看到列表解析的强大之处。




13.生成器表达式


        生成器是特定的函数,容许你返回一个值,而后“暂停”代码的执行,稍后恢复,关于生成器在后面中会有说起,这里介绍的是生成器表达式。

        列表解析的一个不足就是必要生成全部的数据,用以建立整个列表,这可能对有大量数据的迭代器有负面影响(占用大量的内存),生成器表达式经过结合列表解析和生成器解决了这个问题。

        所谓生成器表达式,它并不真正建立数字列表,而是返回一个生成器,这个生成器在每次计算出一个条目后,把这个条目“产生”(yield)出来。即生成器表达式使用了“延迟计算”(lazy evaluation),因此它在使用内存上更有效(好比你想开发一个ftp软件,这就很重要了,由于能够大大节省内存空间)。

        其语法与列表解析对好比下:

# 列表解析
[expr for iter_var in iterable if cond_expr]
# 生成器表达式
(expr for iter_var in iterable if cond_expr)

        固然,生成器并不会让列表解析废弃,它只是一个内存使用更友好的结构。能够看下面的应用例子。


(1)磁盘文件样例

        前面计算文本文件的大小,使用的是下面的方法:

sum([len(word) for line in f for word in line.split()])

        这相关因而把f文件中的全部单词都读取出来,而后放入到列表中,从而生成该文件的一个完整单词列表保存在内存中,对于比较小的文件,这是不错的方法,但若是这个文件自己的大小就已经超出了内存空间的大小,这样你的主机确定就会崩溃了。所以使用生成器表达式就能够解决这个问题:

sum(len(word) for line in f for word in line.split())
# 注意sum的参数不只能够是列表,还能够是可迭代对象,好比生成器表达式

        实际使用以下:

>>> f = open('welcome.txt', 'r')
>>> sum(len(word) for line in f for word in line.split())
68
# 固然也能够计算单词个数
>>> f = open('welcome.txt', 'r')
>>> for word in (word for line in f for word in line.split()):
...   count += 1
>>> count
15


(2)交叉配对样例

        生成器表达式就好像是懒惰的列表解析,由于它不会先去产生一个计算结果,直接你须要使用时它才会去生产,一个交叉配对的例子以下:

>>> rows = [1, 2, 3, 17]
>>> def cols():
...   yield 56
...   yield 2
...   yield 1
... 
>>> x_product_pairs = ((i, j) for i in rows for j in cols())
>>> x_product_pairs
<generator object <genexpr> at 0x7feecdd441e0>
>>> for pair  in x_product_pairs:
...   print pair
... 
(1, 56)
(1, 2)
(1, 1)
(2, 56)
(2, 2)
(2, 1)
(3, 56)
(3, 2)
(3, 1)
(17, 56)
(17, 2)
(17, 1)


(3)重构样例

        主要是考虑内存的节省问题,由于在处理大文件时用普通的迭代方式就须要把文件内容所有读入到内存中,这是须要很是多的内存的,而使用生成器表达式就能够缓解这个问题,以下面一个计算文件最长行字符数的例子:

>>> f = open('welcome.txt', 'r')
>>> longest = max(len(x.strip()) for x in f)
>>> f.close()
>>> longest
48
# 这里改进后的例子,原来是使用列表解析,会占用大量的内存空间,而使用生成器表达式则不会

        固然,重构过程的一个思想方法也是很是不错的,能够参考书上的例子。

        另外,屡次提到读取文件时占用内存的状况,这主要是指不要尝试把一个大文件保存为Python的数据结构,不然会占用大量的内存,使用迭代器自己是能够节省内存的,以下面的一个测试,big_file的大小为1.4GB:

>>> f = open('big_file', 'r')
>>> f2 = open('new_file', 'w')
>>> for line in f:
...   f2.write(line)
... 
>>> f.close()
>>> f2.close()

        用for line in file的方式来读取文件时,其实用的就是迭代器的方式,前面已经有提过,这里的测试结果是,在文件复制的过程当中,内存的占用率并无太大的变化,说明Python解释器每处理一行数据都会将原来缓存在内存中的一行数据删除掉(垃圾回收机制),而后再去读取另一行,这样会得到比较高的性能,而若是直接使用f.readlines()的方法,会发现内存占用立刻变大。

        而使用生成器表达式的状况是针对你但愿经过生成一个列表来统计一些数据时内存占用大的问题,由于生成该列表自己就要把数据缓存到内存中,使用列表解析器时就须要生成一个列表,生成器表达式就是弥补列表解析的不足而提出的。




14.相关模块


        主要是itertools模块,它是迭代器的一些辅助工具。

相关文章
相关标签/搜索