如果想通过操作和处理一个序列(或其他的可迭代对象)来创建一个新的列表时可以使用列表解析( List comprehensions)和生成表达式。同样的还有字典推导式,集合推导式。
list
comprehensions)也叫列表解析式。它的结构是在一个中括号里包含一个表达式,然后是一个for
语句,然后是0个或多个for
或者if
语句。那个表达式可以是任意的,意思是你可以在列表中放入任意类型的对象。返回结果将是一个新的列表,在这个以if
和for
语句为上下文的表达式运行完成之后产生。
(1)提供了一种简明扼要的方法来创建列表。
(2)在需要改变列表而不是需要新建某列表时,可以使用列表解析。
列表解析表达式为:
[expr for iter_var in iterable]
[expr for iter_var in iterable if cond_expr]
第一种语法:首先迭代iterable里所有内容,每一次迭代,都把iterable里相应内容放到iter_var中,再在表达式中应用该iter_var的内容,最后用表达式的计算值生成一个列表(每一次迭代的值组成一个列表)。
第二种语法:加入了判断语句,只有满足条件的内容才把iterable里相应内容放到iter_var中,再在表达式中应用该iter_var的内容,最后用表达式的计算值生成一个列表。
示例:
>>> L= [(x+1,y+1) for x in range(3) for y in range(5)]
>>> L
[(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)]
可以看到迭代顺序是从后往前。先迭代倒数第一个迭代对象,然后迭代倒数第二个迭代对象,...,最后迭代第一个迭代对象。
>>> N=[x+10 for x in range(10) if x>5]
>>> N
[16, 17, 18, 19]
嵌套循环(nested loop)该如何改写为列表解析式呢?
下面是一个拉平(flatten)矩阵(以列表为元素的列表)的for
循环:
flattened = []
for row in matrix:
for n in row:
flattened.append(n)
下面这个列表解析式实现了相同的功能:
flattened = [n for row in matrix for n in row]
列表解析式中的嵌套循环读起来就有点绕口了。
注意:有可能会想把这个列表解析式写成这样:
flattened = [n for n in row for row in matrix]
但是这行代码是错误的。这里我们颠倒了两个for
循环的顺序。正确的代码是之前那个。也就是如果要在列表解析式中处理嵌套循环,请记住for
循环子句的顺序与我们原来for
循环的顺序是一致的。从左到右是第一层循环,第二层循环,...,最后一层循环。
列表推导式可以带任意数量的嵌套 for
循环,并且每一个 for
循环后面都有可选的 if
语句。
通用语法:
[ expression for x in X [if condition]
for y in Y [if condition]
...
for n in N [if condition] ]
例如,下面的代码输出了0~4之间的偶数和奇数的组合。
>>> [(x, y) for x in range(5) if x % 2 == 0 for y in range(5) if y % 2 == 1]
[(0, 1), (0, 3), (2, 1), (2, 3), (4, 1), (4, 3)]
等价于下面的一般 for
循环:
>>> L = []
>>> for x in range(5):
... if x % 2 == 0:
... for y in range(5):
... if y % 2 == 1:
... L.append((x, y))
>>> L
[(0, 1), (0, 3), (2, 1), (2, 3), (4, 1), (4, 3)]
同样地原则也适用集合解析式(set comprehension)和字典解析式(dictionary comprehension)。
dict
comprehensions)字典推导和列表推导的使用方法是类似的,通过迭代生成一个字典。
字典推导式为:
{expr for iter_var in iterable}
{expr for iter_var in iterable if cond_expr}
expr可以操作字典中的key和value。
示例:
mcase = {'a': 10, 'b': 34, 'A': 7, 'Z': 3}
mcase_frequency = {
k.lower(): mcase.get(k.lower(), 0) + mcase.get(k.upper(), 0)
for k in mcase.keys()
}
# mcase_frequency == {'a': 17, 'z': 3, 'b': 34}
在上面的例子中我们把同一个字母但不同大小写的值合并起来了。
你还可以快速对换一个字典的键和值:
{v: k for k, v in some_dict.items()}
set
comprehensions)它们跟列表推导式也是类似的。 唯一的区别在于它们使用大括号{}
。 举个例子:
squared = {x**2 for x in [1, 1, 2]}
print(squared)
# Output: {1, 4}
需要保持集合的特点:不能含有重复元素。
迭代器是一个让程序员可以遍历一个容器(特别是列表)的对象。然而,一个迭代器在遍历并读取一个容器的数据元素时,并不会执行一个迭代。换句话说这里有三个部分:
Python中任意的对象,只要它定义了可以返回一个迭代器的__iter__
方法,或者定义了可以支持下标索引的__getitem__
方法,那么它就是一个可迭代对象。简单说,可迭代对象就是能提供迭代器的任意对象。
任意对象,只要定义了next
(Python2) 或者__next__
方法,它就是一个迭代器。
用简单的话讲,它就是从某个地方(比如一个列表)取出一个元素的过程。当我们使用一个循环来遍历某个东西时,这个过程本身就叫迭代。
生成器也是一种迭代器,但是你只能对其迭代一次。这是因为它们并没有把所有的值存在内存中,而是在运行时生成值。你通过遍历来使用它们,要么用一个“for”循环,要么将它们传递给任意可以进行迭代的函数和结构。
生成器包括生成器表达式(类似于推导式)和生成器函数。
生成器表达式是在python2.4中引入的,当序列过长, 而每次只需要获取一个元素时,应当考虑使用生成器表达式而不是列表解析。生成器表达式的语法和列表解析一样,只不过生成器表达式是被()括起来的,而不是[],如下:
(expr for iter_var in iterable)
(expr for iter_var in iterable if cond_expr)
>>> L= (i +1 for i in range(10) if i %2)
>>> L
at 0xb749a52c>
>>> L1=[]
>>>for i in L:
... L1.append(i)
...
>>> L1
[2, 4, 6, 8, 10]
生成器表达式并不真正创建数字列表, 而是返回一个生成器,这个生成器在每次计算出一个条目后(调用的时候再计算),把这个条目“产生”(yield)出来。 生成器表达式使用了“惰性计算”(lazy evaluation,也有翻译为“延迟求值”,我以为这种按需调用call by need的方式翻译为惰性更好一些),只有在检索时才被赋值( evaluated),所以在列表比较长的情况下使用内存上更有效。
A generator object in python is something like a lazy list. The elements are only evaluated as soon as you iterate over them.
大多数时候生成器是以函数来实现的。然而,它们并不返回一个值,而是yield
(暂且译作“生出”)一个值。
示例1:
def generator_function():
for i in range(10):
yield i
for item in generator_function():
print(item)
# Output: 0
# 1
# 2
# 3
# 4
# 5
# 6
# 7
# 8
# 9
示例2:计算斐波那契数列的生成器:。
# generator version
def fibon(n):
a = b = 1
for i in range(n):
yield a
a, b = b, a + b
函数使用方法如下:
for x in fibon(1000000):
print(x)
我们已经讨论过生成器使用一次迭代。在测试前你需要再知道一个Python内置函数:next()
。它允许我们获取一个序列的下一个元素。那我们来验证下我们的理解:
def generator_function():
for i in range(3):
yield i
gen = generator_function()
print(next(gen))
# Output: 0
print(next(gen))
# Output: 1
print(next(gen))
# Output: 2
print(next(gen))
# Output: Traceback (most recent call last):
# File "", line 1, in
# StopIteration
我们可以看到,在yield
掉所有的值后,next()
触发了一个StopIteration
的异常。基本上这个异常告诉我们,所有的值都已经被yield
完了。
为什么我们在使用for
循环时没有这个异常呢?因为for
循环会自动捕捉到这个异常并停止调用next()
。
实际上Python中一些内置数据类型也支持迭代。我们这就去看看:
my_string = "Yasoob"
next(my_string)
# Output: Traceback (most recent call last):
# File "", line 1, in
# TypeError: str object is not an iterator
好吧,这不是我们预期的。这个异常说那个str
对象不是一个迭代器。对,就是这样!它是一个可迭代对象,而不是一个迭代器。这意味着它支持迭代,但我们不能直接对其进行迭代操作。那我们怎样才能对它实施迭代呢?是时候学习下另一个内置函数,iter
。它将根据一个可迭代对象返回一个迭代器对象。这里是我们如何使用它:
my_string = "Yasoob"
my_iter = iter(my_string)
next(my_iter)
# Output: 'Y'
生成器最佳应用场景是:不想同一时间将所有计算出来的大量结果集分配到内存当中,特别是结果集里还包含循环。特别是循环很大,计算量也很大。
许多Python 2里的标准库函数都会返回列表,而Python 3都修改成了返回生成器,因为生成器占用更少的资源。
多用于循环生成一组数值。
range
函数说明:range([start,] stop[, step]),根据start与stop指定的范围以及step设定的步长,生成一个序列。不包含stop。
start 的默认值为 0。唯一要求的参数值是 stop,产生的最后一个数的值是 stop 的前一个,并且 step 的默认值是 1。当然,也可以反向创建自然数序列,这时 step 的值为 -1。
像 zip()、range() 这些函数返回的是一个可迭代的对象,所以可以使用 for ... in 的结构遍历,或者把这个对象转化为一个序列(例如列表)。
range示例:
>>> range(5)
[0, 1, 2, 3, 4]
>>> range(1,5)
[1, 2, 3, 4]
>>> range(0,6,2)
[0, 2, 4]
>>> range(0,-10,-1) #起点是1,终点是10,步长为-1
[0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
>>> range(0,-10,1) #起点是0,终点是-10,终点为负数时,步长只能为负数,否则返回空
[]
>>> range(0) #起点是0,返回空列表
[]
>>> range(1,0) #起点大于终点,返回空列表
[]
xrange
函数说明:用法与range完全相同,所不同的是生成的不是一个数组,而是一个生成器。不包含stop。
xrange示例:
>>> xrange(5)
xrange(5)
>>> list(xrange(5))
[0, 1, 2, 3, 4]
>>> xrange(1,5)
xrange(1, 5)
>>> list(xrange(1,5))
[1, 2, 3, 4]
>>> xrange(0,6,2)
xrange(0, 6, 2)
>>> list(xrange(0,6,2))
[0, 2, 4]
所以不同点就是range类似于推导式,而xrange则是生成器。range产生一个列表,而xrange则是在使用的时候产生每一个列表值。所以range需要为产生的列表开辟所有内存,并且产生整个列表,而xrange只在调用时产生每一个值。
由上面的示例可以知道:要生成很大的数字序列的时候,用xrange会比range性能优很多,因为不需要一上来就开辟一块很大的内存空间。在小序列时,两者区别不大。除非是要返回一个列表,所以推荐使用xrange函数。Python3 里,range 已经被 xrange 取代了。
1. 当需要只是执行一个循环的时候尽量使用循环而不是列表解析,这样更符合python提倡的直观性。
2. 当有内建的操作或者类型能够以更直接的方式实现的,不要使用列表解析。
例如复制一个列表时,使用:L1=list(L)即可,不必使用:L1=[x for x in L]
3. 当序列过长, 而每次只需要获取一个元素时,使用生成器表达式。
4. 列表解析的性能相比要比map要好,实现相同功能的for循环效率最差(和列表解析相比差两倍)。
5. 列表解析可以转换为 for循环或者使用map(其中可能会用到filter、lambda函数)表达式,但是列表解析更为简单明了,后者会带来更复杂和深层的嵌套。
6. 解析式如果很长,可以利用断行(换行)来增加代码的可读性。