"I give you a new commandment, that you love one another. Just as I have loved you, you also should love one another. By this everyone will know that you are my disciples, if you have love for one another." (JOHN 14:34-35)

语句(4)

for循环在Python中应用广泛,所以,要用更多的篇幅来介绍。

并行迭代

关于迭代,在《列表(2)》中曾经提到过“可迭代的(iterable)”这个词,并给予了适当解释,这里再次提到“迭代”,说明它在Python中占有重要的位置。

迭代,在Python中表现就是用for循环,从序列对象中获得一定数量的元素。

在前面一节中,用for循环来获得列表、字符串、元组,乃至于字典的键值对,都是迭代。

现实中迭代不都是那么简单的,比如这个问题:

问题:有两个列表,分别是:a = [1, 2, 3, 4, 5], b = [9, 8, 7, 6, 5],要计算这两个列表中对应元素的和。

解析:

太简单了,一看就知道结果了。

很好,这是你的方法,如果是让Python来做,应该怎么做呢?

观察发现两个列表的长度一样,都是5。那么对应元素求和,就是相同的索引值对应的元素求和,即a[i]+b[i](i=0,1,2,3,4),这样一个一个地就把相应元素和求出来了。当然,要用for来做这个事情了。

>>> a = [1, 2, 3, 4, 5]
>>> b = [9, 8, 7, 6, 5]
>>> c = []
>>> for i in range(len(a)):
...     c.append(a[i]+b[i])
... 
>>> c
[10, 10, 10, 10, 10]

看来for的表现还不错。

不过,这种方法虽然解决问题了,但Python总不会局限于一个解决之道,而且,用Python编程,还要追求优雅。

于是,zip()——一个内建函数姗姗来迟,它可以让同样的问题有不一样的解决途径,看起来更有范儿。

先要看一看zip()的文档,以了解它的身世。

在Python 2中,是这样描述的:

>>> help(zip)
Help on built-in function zip in module __builtin__:

zip(...)
    zip(seq1 [, seq2 [...]]) -> [(seq1[0], seq2[0] ...), (...)]

    Return a list of tuples, where each tuple contains the i-th element
    from each of the argument sequences.  The returned list is truncated
    in length to the length of the shortest argument sequence.

在Python 3中,相比Python 2,有一些稍微的差别。

>>> help(zip)
Help on class zip in module builtins:

class zip(object)
 |  zip(iter1 [,iter2 [...]]) --> zip object
 |  
 |  Return a zip object whose .__next__() method returns a tuple where
 |  the i-th element comes from the i-th iterable argument.  The .__next__()
 |  method continues until the shortest iterable in the argument sequence
 |  is exhausted and then it raises StopIteration.

Python 2中,参数是seq1, seq2, ...,意思是序列数据;在Python 3中,参数需要时可迭代对象。这点差别,通常是没有什么影响的,因为序列也是可迭代的。值得关注的是返回值,在Python 2中,返回值是一个列表对象,里面以元组为元素;而Python 3中返回的是一个zip对象。

通过实验来理解上面的文档:

Python 2:

>>> a = "qiwsir"
>>> b = "github"
>>> zip(a, b)
[('q', 'g'), ('i', 'i'), ('w', 't'), ('s', 'h'), ('i', 'u'), ('r', 'b')]

Python 3:

>>> zip(a, b)
<zip object at 0x0000000003521D08>
>>> list(zip(a, b))
[('q', 'g'), ('i', 'i'), ('w', 't'), ('s', 'h'), ('i', 'u'), ('r', 'b')]

如果序列长度不同,那么就以"the length of the shortest argument sequence"为准。

>>> c = [1, 2, 3]
>>> d = [9, 8, 7, 6]
>>> zip(c, d)        #这是Python 2的结果,如果是Python 3,请仿照前面的方式显示查看
[(1, 9), (2, 8), (3, 7)]

>>> m = {"name", "lang"}  
>>> n = {"qiwsir", "python"}
>>> zip(m, n)        #这是Python 2的结果,如果是Python 3,请仿照前面的方式显示查看
[('lang', 'python'), ('name', 'qiwsir')]

m,n是字典吗?当然不是。下面的才是字典呢。

>>> s = {"name":"qiwsir"}
>>> t = {"lang":"python"}
>>> zip(s,t)
[('name', 'lang')]

zip是一个内置函数,它的参数必须是序列,如果是字典,那么键视为序列。然后将序列对应的元素依次组成元组,做为一个列表的元素。

下面是比较特殊的情况,参数是一个序列对象的时候,生成的结果样子:

>>> a ='qiwsir'
>>> c = [1, 2, 3]
>>> zip(c)        #这是Python 2的结果,如果是Python 3,请仿照前面的方式显示查看
[(1,), (2,), (3,)]
>>> zip(a)
[('q',), ('i',), ('w',), ('s',), ('i',), ('r',)]

很好的zip()!那么就用它来解决前面那个两个列表中值对应相加吧。

>>> d = []
>>> for x,y in zip(a,b):
...     d.append(x+y)
... 
>>> d
[10, 10, 10, 10, 10]

多么优雅的解决!

比较这个问题的两种解法,似乎第一种解法适用面较窄,比如,如果已知给定的两个列表长度不同,第一种解法就出问题了。而第二种解法还可以继续适用。的确如此,不过,第一种解法也不是不能修订的。

>>> a = [1, 2, 3, 4, 5]
>>> b = ["python","www.itdiffer.com","qiwsir"]

如果已知是这样两个列表,要讲对应的元素“加起来”。

>>> length = len(a) if len(a)<len(b) else len(b)
>>> length
3

首先用这种方法获得两个列表中最短的那个列表的长度。看那句三元操作,这是非常pythonic的写法啦。写出这句,就可以冒充高手了。哈哈。

>>> for i in range(length):
...     c.append(str(a[i]) + ":" + b[i])
... 
>>> c
['1:python', '2:www.itdiffer.com', '3:qiwsir']

我还是用第一个思路做的,经过这么修正一下,也还能用。要注意一个细节,在“加”的时候,不能直接用a[i],因为它引用的对象是一个整数类型,不能跟后面的字符串类型相加,必须转化一下。

当然,zip()也是能解决这个问题的。

>>> d = []
>>> for x,y in zip(a, b):
...     d.append(x + y)
... 
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'

报错!看错误信息,我刚刚提醒的那个问题就冒出来了。所以,应该这么做:

>>> for x,y in zip(a, b):
...     d.append(str(x) + ":" + y)
... 
>>> d
['1:python', '2:www.itdiffer.com', '3:qiwsir']

这才得到了正确结果。

切记:computer是一个姑娘,她非常秀气,需要敲代码的小伙子们耐心地、细心地跟她相处。

以上两种写法那个更好呢?前者?后者?

>>> result = [(2, 11), (4, 13), (6, 15), (8, 17)]

>>> zip(*result)
[(2, 4, 6, 8), (11, 13, 15, 17)]

zip()还能这么干,是不是有点意思?

下面延伸一个问题:

问题:有一个字典,myinfor = {"name":"qiwsir", "site":"qiwsir.github.io", "lang":"python"},将这个字典变换成:infor = {"qiwsir":"name", "qiwsir.github.io":"site", "python":"lang"}

解析:

解法一,用for循环:

>>> myinfor = {"name":"qiwsir", "site":"qiwsir.github.io", "lang":"python"}
>>> infor = {}
>>> for k,v in myinfor.items():
...     infor[v]=k
... 
>>> infor
{'python': 'lang', 'qiwsir.github.io': 'site', 'qiwsir': 'name'}

解法二,用zip()

>>> dict(zip(myinfor.values(), myinfor.keys()))
{'python': 'lang', 'qiwsir.github.io': 'site', 'qiwsir': 'name'}

呜呼,这是什么情况?原来这个zip()还能这样。

为了能够窥探内部的奥秘,我们将那一行分解开来。

>>> myinfor.values()              #得到字典值的列表
['python', 'qiwsir', 'qiwsir.github.io']
>>> myinfor.keys()                #得到字典键的列表
['lang', 'name', 'site']
>>> temp = zip(myinfor.values(), myinfor.keys())     #压缩成一个列表,每个元素是一个元组,元组中第一个是值,第二个是键
>>> temp                                                                                   #Python 3中是一个zip对象
[('python', 'lang'), ('qiwsir', 'name'), ('qiwsir.github.io', 'site')]
>>> dict(temp)                          #这是函数dict()的功能,将上述列表转化为字典
{'python': 'lang', 'qiwsir.github.io': 'site', 'qiwsir': 'name'}

至此,是不是明白zip()和循环的关系了呢?有了它可以让某些循环简化。

enumerate

enumerate()也是内建函数。

本来我们可以通过for i in range(len(list))的方式得到一个列表的每个元素对应的索引,然后在用list[i]的方式得到该元素。这是前面用过的路数。

但是,如果要同时得到元素索引和元素怎么办?

>>> week = ['monday', 'sunday', 'friday']
>>> for i in range(len(week)):
...     print week[i]+' is '+str(i)     #注意,i是int类型,如果和前面的用+连接,必须是str类型
...                                                           #如果使用Python 3,请自行更换为print(week[i]+' is '+str(i))
monday is 0
sunday is 1
friday is 2

内建函数enumerate,能够实现类似的功能,并且简化。

>>> for (i, day) in enumerate(week):
...     print day+' is '+str(i)        #Python 3: print(day+' is '+str(i))
... 
monday is 0
sunday is 1
friday is 2

官方文档是这么说的:

Return an enumerate object. sequence must be a sequence, an iterator, or some other object which supports iteration. The next() method of the iterator returned by enumerate() returns a tuple containing a count (from start which defaults to 0) and the values obtained from iterating over sequence:

顺便抄录几个例子,供看官欣赏,最好实验一下。

>>> seasons = ['Spring', 'Summer', 'Fall', 'Winter']
>>> list(enumerate(seasons))
[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]

>>> list(enumerate(seasons, start=1))
[(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')]

对于这样一个列表:

>>> mylist = ["qiwsir",703,"python"]
>>> enumerate(mylist)
<enumerate object at 0xb74a63c4>    
>>> list(enumerate(mylist))
[(0, 'qiwsir'), (1, 703), (2, 'python')]

再设计一个小问题,练习一下这个函数。

问题:将字符串中的某些字符替换为其它的字符串。原始字符串"Do you love Canglaoshi? Canglaoshi is a good teacher.",请将"Canglaoshi"替换为"PHP".

解析:

>>> raw = "Do you love Canglaoshi? Canglaoshi is a good teacher."

这是所要求的那个字符串,但是,不能直接对这个字符串使用enumerate(),因为它会变成这样:

>>> list(enumerate(raw))
[(0, 'D'), (1, 'o'), (2, ' '), (3, 'y'), (4, 'o'), (5, 'u'), (6, ' '), (7, 'l'), (8, 'o'), (9, 'v'), (10, 'e'), (11, ' '), (12, 'C'), (13, 'a'), (14, 'n'), (15, 'g'), (16, 'l'), (17, 'a'), (18, 'o'), (19, 's'), (20, 'h'), (21, 'i'), (22, '?'), (23, ' '), (24, 'C'), (25, 'a'), (26, 'n'), (27, 'g'), (28, 'l'), (29, 'a'), (30, 'o'), (31, 's'), (32, 'h'), (33, 'i'), (34, ' '), (35, 'i'), (36, 's'), (37, ' '), (38, 'a'), (39, ' '), (40, 'g'), (41, 'o'), (42, 'o'), (43, 'd'), (44, ' '), (45, 't'), (46, 'e'), (47, 'a'), (48, 'c'), (49, 'h'), (50, 'e'), (51, 'r'), (52, '.')]

这不是所需要的。所以,先把raw转化为列表:

>>> raw_lst = raw.split(" ")

然后用enumerate()

>>> for i, string in enumerate(raw_lst):
...     if string == "Canglaoshi":
...         raw_lst[i] = "PHP"
... 

没有什么异常现象,查看一下那个raw_lst列表,看看是不是把"Canglaoshi"替换为"PHP"了。

>>> raw_lst
['Do', 'you', 'love', 'Canglaoshi?', 'PHP', 'is', 'a', 'good', 'teacher.']

只替换了一个,还有一个没有替换。为什么?仔细观察发现,没有替换的那个是'Canglaoshi?',跟条件判断中的"Canglaoshi"不一样。

修改一下,把条件放宽:

>>> for i, string in enumerate(raw_lst):
...     if "Canglaoshi" in string:
...         raw_lst[i] = "PHP"
... 
>>> raw_lst
['Do', 'you', 'love', 'PHP', 'PHP', 'is', 'a', 'good', 'teacher.']

好的。然后呢?再转化为字符串?留给读者试试。

列表解析

先看下面的例子,这个例子是想得到1到9的每个整数的平方,并且将结果放在列表中,打印出来。

>>> power2 = []
>>> for i in range(1, 10):
...     power2.append(i*i)
... 
>>> power2
[1, 4, 9, 16, 25, 36, 49, 64, 81]

Python有一个非常强大的功能,就是列表解析,它这样使用:

>>> squares = [x**2 for x in range(1, 10)]
>>> squares
[1, 4, 9, 16, 25, 36, 49, 64, 81]

看到这个结果,读者还不惊叹吗?

这就是Python,追求简洁优雅的Python!

其官方文档中有这样一段描述,道出了列表解析的真谛:

List comprehensions provide a concise way to create lists. Common applications are to make new lists where each element is the result of some operations applied to each member of another sequence or iterable, or to create a subsequence of those elements that satisfy a certain condition.

这就是Python有意思的地方,也是计算机高级语言编程有意思的地方,你只要动脑筋,总能找到惊喜的东西。

其实,不仅仅对数字组成的列表,所有的都可以如此操作。请在平复了激动的心之后,默默地看下面的代码,感悟一下列表解析的魅力。

>>> mybag = [' glass',' apple','green leaf ']   #有的前面有空格,有的后面有空格
>>> [one.strip() for one in mybag]              #去掉元素前后的空格
['glass', 'apple', 'green leaf']

本节中已经演示过的问题,都能用列表解析来重写。读者不妨试试。

在很多情况下,列表解析的执行效率高,代码简洁明了。是实际写程序中经常被用到的。

现在Python的两个版本,对列表解释上,还是有一点点差别的,请认真看下面的比较操作。

Python 2:

>>> i = 1
>>> [ i for i in range(9)]
[0, 1, 2, 3, 4, 5, 6, 7, 8]
>>> i
8

Python 3:

>>> i = 1
>>> [i for i in range(9)]
[0, 1, 2, 3, 4, 5, 6, 7, 8]
>>> i
1

有没有观察到区别?

i = 1,然后是一个列表解析式,非常巧合的是,列表解析式中也用了变量i。这种情况,在编程中是常常遇到的,我们通常把i=1中的变量i称为处于全局命名空间里面(命名空间,是一个新词汇,暂且用起来,后面会讲述),而列表解析式中的变量i是在列表解析内,称为处在局部命名空间。在Python 3中,for循环里的变量不再与全局命名空间的变量有关联了。这种改变,窃以为,是进步。

对于循环,除了for之外,还有一个叫做while。


总目录   |   上节:语句(3)   |   下节:语句(5)

如果你认为有必要打赏我,请通过支付宝:[email protected],不胜感激。

results matching ""

    No results matching ""