“专业人士笔记”系列目录:
创帆云:Python成为专业人士笔记--强烈建议收藏!每日持续更新!Python中的列表生成式是简洁的语法结构。通过对列表中的每个元素应用函数,可以利用它们从其他列表生成列表。下面的部分将解释和演示这些表达式的用法。
生成式语法
列表生成式通过对iterable的每个元素应用一个表达式来创建一个新列表。最基本的形式是
[ <表达式> for <元素> in <迭代器> ]
还有一个可选的“if”条件
[ <表达式> for <元素> in <迭代器> if <条件> ]
创建一个整数平方的list :
squares = [x * x for x in (1, 2, 3, 4)]
#squares: [1, 4, 9, 16]
for表达式依次先将x设置为(1、2、3、4)中的每个值。表达式x * x的结果被附加到一个内部列表中。内部列表在计算完成时分配给最终的数值并返回
上面的代码会提高运算速度,它的计算过程类似下面代码:
squares = []
for x in (1, 2, 3, 4):
squares.append(x * x)
# squares: [1, 4, 9, 16]
应用于每个元素的表达式可以根据业务逻辑写得更加复杂:
#从字符串中获取大写字符列表
[s.upper() for s in "Hello World"]
# ['H', 'E', 'L', 'L', 'O', ' ', 'W', 'O', 'R', 'L', 'D']
# 在列表中去掉字符串末尾的逗号
[w.strip(',') for w in ['these,', 'words,,', 'mostly', 'have,commas,']]
# ['these', 'words', 'mostly', 'have,commas']
# 更合理地组织单词中的字母——按字母顺序排列
sentence = "Beautiful is better than ugly"
[""
.join(sorted(word, key = lambda x: x.lower())) for word in sentence.split()]
# ['aBefiltuu', 'is', 'beertt', 'ahnt', 'gluy']
else用法
else可以在List表达式结构中使用,但是在语法上要注意: if / else子句应在for循环之前使用,而不是在它之后:
# 在apple中创建一个字符列表,用“*”替换非元音字母
# 结果- 'apple' --> ['a', ' * ', ' * ', '*' ,'e']
[x for x in 'apple' if x in 'aeiou' else '*']
# 报语法错误 SyntaxError: invalid syntax
#当一起使用if / else时,请在循环前使用它们
[x if x in 'aeiou' else '*' for x in 'apple']
#输出:['a', ' * ', ' * ', '*', 'e']
注意,它使用了一个不同的语言结构,条件表达式本身不是生成式语法的一部分。而for in后面的if是列表生成式的一部分,用于过滤来自源iterable的元素,因此遇到else语句必须改变代码的写法
双重迭代
重复迭代的顺序 [… for x in … for y in …] 要么是直观的,要么是违反直觉的。经验法则告诉我们,所有的循环按照for循环一层一层往下读:
def foo(i):
return i, i + 0.5
for i in range(3):
for x in foo(i):
yield str(x)
#等效于:
[str(x)
for i in range(3)
for x in foo(i)
]
最后就可以将其压缩为一行:[str(x) for i in range(3) for x in foo(i)]
替换函数导致的副作用
在使用列表生成式之前,请理解调用它们的副作用(如替换函数,其通常返回None)和返回我们预期结果之间的区别。
许多函数只是接受一个对象并返回某个对象。替换函数修改现有对象,这就是副作用。其他例子包括对象的输入和输出操作,如打印。sort()对列表进行排序并替换原始列表值,返回了一个None值。因此,它不会像预期的那样工作
[x.sort() for x in [[2, 1], [4, 3], [0, 1]]]
# [None, None, None]
相反,sorted()函数返回一个已排序的列表,而不是直接排序并替换原始值:
[sorted(x) for x in [[2, 1], [4, 3], [0, 1]]]
#[[1, 2], [3, 4], [0, 1]]
在某些情况下,替换函数适用于列表生成式。randrange()的副作用是改变随机数生成器的状态,但它也返回了我们想要的值。此外,可以在迭代器上调用next()。下面的随机值生成器是比较适用于列表生成式的情况:
from random import randrange
[randrange(1, 7) for _ in range(10)]
# [2, 3, 2, 1, 1, 5, 2, 4, 3, 5]
列表生成式中的空白符号
更复杂的列表生成式可能达到不希望的长度,或者可读性更差。虽然在示例中不太常见,但是可以将列表生成式分解成多个类似这样的行 :
[
x for x
in 'foo'
if x not in 'bar'
]
列表生成式条件语句
给定一个列表生成式,您可以附加一个或多个if条件来过滤值。
[ <表达式> for <元素> in <迭代器> if <条件> ]
例如,它用于从整数序列中提取偶数 :
[x for x in range(10) if x % 2 == 0]
#输出 [0, 2, 4, 6, 8]
上述代码等价于:
even_numbers = []
for x in range(10):
if x % 2 == 0:
even_numbers.append(x)
print(even_numbers)
#输出: [0, 2, 4, 6, 8]
两个代码尽管提供了相同的结果,但请注意,前一个示例几乎比后一个示例快两倍。对于那些好奇的人来说,这一点很重要。请注意,这与可以用于列表生成式部分的if else条件表达式(有时称为三元表达式)有很大的不同。考虑以下示例:
[x if x % 2 == 0 else None for x in range(10)]
#输出: [0, None, 2, None, 4, None, 6, None, 8, None]
这里的条件表达式不是一个过滤器,而是一个决定列表项使用的哪个值的操作符
如果将它与其他操作符结合起来,这一点就更加明显了
[2 * (x if x % 2 == 0 else -1) + 1 for x in range(10)]
#输出: [1, -1, 5, -1, 9, -1, 13, -1, 17, -1]
还可以组合三元表达式和if条件:
[x if x > 2 else '*' for x in range(10) if x % 2 == 0]
#输出: ['*', '*', 4, 6, 8]
避免使用条件语句进行重复和耗时操作
考虑下面的列表生成式:
def f(x):
import time
time.sleep(.1) #模拟耗时操作
return x**2
[f(x) for x in range(1000) if f(x) > 10]
# [16, 25, 36, …]
这将导致两次调用f(x)来获取1,000个x值:一次调用用于生成值,另一次调用用于检查if条件。如果f(x)是一个特别耗时的操作,这可能会对性能产生重大影响。更糟糕的是,如果调用f()还有副作用(见前章节),则会产生令人吃惊的结果。
相反,应该通过生成一个中间的变量迭代器,对每个x值只计算一次耗时的操作
def f(x):
import time
time.sleep(.1) #模拟耗时操作
return x**2
[v for v in (f(x) for x in range(1000)) if v > 10]
#[16, 25, 36, …]
或者使用内置 map:
def f(x):
import time
time.sleep(.1) # 模拟耗时操作
return x ** 2
x=[v for v in map(f, range(10)) if v > 10]
print(x)
#输出 [16, 25, 36, …]
#注: map() 会根据提供的函数对指定序列做映射并返回list列表(pyton3是返回列表迭代器)
另一种使代码可读性更好的方法是将部分结果(前一个示例中的v)放在一个可迭代的(如列表或元组)中,然后对其进行迭代。由于v将是迭代器中惟一的元素,所以结果是我们现在有了一个对 f 函数输出的引用,这个引用只计算一次 :
def f(x):
import time
time.sleep(.1) # 模拟耗时操作
return x ** 2
x=[v for x in range(1000) for v in [f(x)] if v > 10]
print(x)
# [16, 25, 36, …]
然而,在实践中,代码的逻辑可能更复杂,保持代码的可读性很重要。通常,建议在复杂的程序上使用单独的生成器函数:
def f(x):
import time
time.sleep(.1) # 模拟耗时操作
return x ** 2
#此函数专用于进行迭代逻辑处理,实际代码可能复杂很多
def process_prime_numbers(iterable):
for x in iterable:
if is_prime(x): #此函数用于处理内部复杂逻辑
yield f(x)
[x for x in process_prime_numbers(range(1000)) if x > 10]
# [11, 13, 17, 19, …]
Dictionary 字典生成式
字典生成式和列表生成式语法类似, 只是它生成的是字典对象而不是列表 。
一个基本的例子:
#生成每个数及对应平方数字典
m={x: x * x for x in (1, 2, 3, 4)}
print(m)
#输出:{1: 1, 2: 4, 3: 9, 4: 16}
还有另一种写法:
m=dict((x, x * x) for x in (1, 2, 3, 4))
print(m)
#输出:{1: 1, 2: 4, 3: 9, 4: 16}
与列表生成式一样,我们可以在dict生成式中使用条件语句来生成满足某些条件的dict元素。
m={name: len(name) for name in ('Stack', 'Overflow', 'Exchange') if len(name) > 6}
print(m)
#输出:{'Overflow': 8, 'Exchange': 8}
或者,使用生成器表达式重写。
dict((name, len(name)) for name in ('Stack', 'Overflow', 'Exchange') if len(name) > 6)
# {'Exchange': 8, 'Overflow': 8}
从字典开始,使用字典生成式作为键-值对过滤器 :
initial_dict = {'x': 1, 'y': 2}
m={key: value for key, value in initial_dict.items() if key == 'x'}
print(m)
#输出:{'x': 1}
字典转换键和值(反转字典)
如果您有一个包含简单hash值的dict:
my_dict = {1: 'a', 2: 'b', 3: 'c'}
你想要交换键和值的位置,你可以根据你的编码风格采取几种方法 :
swapped = {v: k for k, v in my_dict.items()}
swapped = dict((v, k) for k, v in my_dict.iteritems())
swapped = dict(zip(my_dict.values(), my_dict))
swapped = dict(zip(my_dict.values(), my_dict.keys()))
swapped = dict(map(reversed, my_dict.items()))
print(swapped)
#输出: {a: 1, b: 2, c: 3}
合并字典
组合两个字典并使用嵌套字典生成式重写旧值:
dict1 = {'w': 1, 'x': 1}
dict2 = {'x': 2, 'y': 2, 'z': 2}
{k: v for d in [dict1, dict2] for k, v in d.items()}
# {'w': 1, 'x': 2, 'y': 2, 'z': 2}
然而,字典解析(PEP 448)可能是首选:
dict1 = {'w': 1, 'x': 1}
dict2 = {'x': 2, 'y': 2, 'z': 2,'w':3} #注意:w为两个字典都有
m={**dict1, **dict2}
print(m)
#输出:{'w': 3, 'x': 2, 'y': 2, 'z': 2} 看到w被第2个字典覆盖了
注意:dictionary字典生成式是在Python 3.0中添加的,并向后移植到2.7+,而list列表推导式是在2.0中添加的。版本< 2.7可以使用生成器表达式和dict()构建来模拟字典生成式的行为。
列表生成式与嵌套循环
列表生成式可以使用嵌套的for循环。您可以在列表生成式中编写任意数量的嵌套的for循环,并且每个for循环可以有一个可选的关联if进行筛选。这样做时,for的构造顺序为顺次添加。列表生成式的一般结构是这样的 :
[ expression for target1 in iterable1 [if condition1]
for target2 in iterable2 [if condition2]…
for targetN in iterableN [if conditionN] ]
例如,下面的代码使用多个For语句平展列表 :
data = [[1, 2], [3, 4], [5, 6]]
output = []
for each_list in data:
for element in each_list:
output.append(element)
print(output)
#输出: [1, 2, 3, 4, 5, 6]
可以等价地写成一个包含多个for结构的列表生成式:
data = [[1, 2], [3, 4], [5, 6]]
output = [element for each_list in data for element in each_list]
print(output)
#输出: [1, 2, 3, 4, 5, 6]
在列表生成式中,外部循环(第一个 for语句)都位于最前面。除了更紧凑之外,理解上也要快得多。
内联if的嵌套方式类似,可以出现在第一个for之后的任何位置
data = [[1], [2, 3], [4, 5]]
output = [element for each_list in data
if len(each_list) == 2
for element in each_list
if element != 5]
print(output)
#输出: [2, 3, 4]
但是, 在嵌套深度超过2级,and/or等逻辑过于复杂 出于可读性考虑,应优先考虑使用传统的for循环 。 多个嵌套循环列表生成式可能容易出错,或者产生意外结果
生成器表达式
生成器表达式非常类似于列表生成式。主要的区别在于,它不会一次创建完整的结果集;它创建了一个可以迭代的生成器对象。例如,查看以下代码中的差异 :
#list 列表生成式
[x**2 for x in range(10)]
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
#生成器表达式
(x**2 for x in xrange(10))
# <generator object <genexpr> at 0x11b4b7c8
#注意:在python3中, 取消了 range 函数,而把 xrange 函数重命名为 range,所以现在直接用 range 函数即可
这是两个不同的对象:
1、列表生成式返回一个列表对象,而生成器表达式返回一个生成器。
2、生成器对象不能被索引并使用next函数来按顺序获取项目。
注意:我们使用xrange,因为它也创建了一个生成器对象(在python3中用range即直接返回生成器对象)。如果我们在python2中使用range,就会创建一个列表。而且,xrange只存在于python 2的后续版本中。有关更多信息,请参见range和xrange函数示例之间的区别。
g = (x**2 for x in range(10))
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(g.__next__())
#输出:
0
1
4
9
16
#另外一种方法,可以一次性输出
for i in [x ** 2 for x in range(10)]:
print(i)
使用案例
生成器表达式是延迟计算的,这意味着它们只在迭代时生成和返回每个值。这在遍历大型数据集时非常有用,避免了在内存中创建超大数据集副本的情况
for square in (x**2 for x in range(1000000)):
#业务逻辑代码
另一个常见的用例是,如果没有必要,则避免遍历整个数据集。在下面的例子中,使用get objects()的每次迭代从远程API检索项。可能存在数千个对象,必须逐个检索,并且我们只需要知道是否存在匹配的对象。当我们计算匹配对照时,可以使用生成器表达式
def get_objects():
""" 逐个从API获取对象 """
while True:
yield get_next_item()
def object_matches_pattern(obj):
#执行复杂的计算
return matches_pattern
def right_item_exists():
items = (object_matched_pattern(each) for each in get_objects())
for item in items:
if item.is_the_right_one:
return True
return False
Set 集合生成式
集合生成式类似于列表生成式和字典生成式,但是它产生一个集合,集合是元素唯一的无序集合
# 包含range(5)内所有值的集合 :
a={x for x in range(5)}
print(a)
# 输出: {0, 1, 2, 3, 4}
# 在1和10之间的一组偶数 :
b={x for x in range(1, 11) if x % 2 == 0}
print(b)
# 输出: {2, 4, 6, 8, 10}
# 文本字符串中唯一的字母字符 :
text = "When in the Course of human events it becomes necessary for one people..."
c={ch.lower() for ch in text if ch.isalpha()}
print(c)
# 输出: set(['a', 'c', 'b', 'e', 'f', 'i', 'h', 'm', 'l', 'o',
# 'n', 'p', 's', 'r', 'u', 't', 'w', 'v', 'y'])
# 每次生成顺序可能不一样
重构过滤器并映射到列表生成式
应该用列表生成式替换 filter 或 map 函数 。Guido Van Rossum在2005年的一封公开信中描述了这一点
下面的代码行被认为是“不符合python风格”的,并且会在许多python程序中引起错误:
filter(lambda x: x % 2 == 0, range(10)) # 偶数 < 10
map(lambda x: 2*x, range(10)) # 把每个数字都乘以二
根据我们从前面的引用中学到的知识,我们可以将这些过滤器和映射表达式分解为它们等价的列表生成式;并从每个表达式中删除lambda函数——使代码在过程中更具可读性,代码转换如下:
# Filter:
#P(x) = x % 2 == 0
#S = range(10)
[x for x in range(10) if x % 2 == 0]
#Map
#F(x) = 2*x
#S = range(10)
[2*x for x in range(10)]
在处理链接函数时,可读性变得更加重要。由于可读性的原因,一个map或filter函数的结果应该作为中间结果传递给下一个;对于简单的情况,可以用单个列表生成式来替换它们。此外,我们可以很容易地从列表中看出我们的过程的结果是什么,在对链式map和filter过程进行推理时,哪个环节有更大的运算负荷
# Map & Filter
filtered = filter(lambda x: x % 2 == 0, range(10))
results = map(lambda x: 2 * x, filtered)
print(list(results))
# List 生成式
results = [2 * x for x in range(10) if x % 2 == 0]
print(results)
#它们都输出:
[0, 4, 8, 12, 16]
重构-快速参考
Map
map(F, S) == [F(x) for x in S] #用于将结果映射到一个迭代器对象(python3),再转换成其他类型输出
Filter
filter(P, S) == [x for x in S if P(x)] #用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的迭代器对象(python3)
其中F和P是分别转换输入值和返回bool的函数
包含tuples元组的生成式
列表生成式的for子句可以指定多个变量 :
[x + y for x, y in [(1, 2), (3, 4), (5, 6)]]
# [3, 7, 11]
[x + y for x, y in zip([1, 3, 5], [2, 4, 6])]
# [3, 7, 11]
这就像普通的for循环 :
for x, y in [(1,2), (3,4), (5,6)]:
print(x+y)
#输出:
3
7
11
但是请注意,如果生成式的表达式是一个元组,那么它必须加上括号 :
[x, y for x, y in [(1, 2), (3, 4), (5, 6)]]
#语法错误,报SyntaxError: invalid syntax
[(x, y) for x, y in [(1, 2), (3, 4), (5, 6)]]
Out: [(1, 2), (3, 4), (5, 6)]
利用生成式计数
当我们想要计算一个可迭代的满足某些条件的元素数量时,我们可以使用生成式来完成这个工作:
#计算range(1000)中包含数字9的偶数的个数
print (sum(
1 for x in range(1000)
if x % 2 == 0 and
'9' in str(x)
))
#输出: 95
其计算过程可以概括为 :
- 遍历范围1000以内的元素。
- 连接所有需要的if条件。
- 使用1作为表达式,为每个满足条件的项返回1
- 将所有的1相加,以确定满足条件的个数
注意:这里我们不是在list列表中收集1(注意没有方括号),而是直接将这些1传递给求和sum函数。这被称为生成器表达式,类似于生成式。
更改list列表元素的数据类型
数值型数据通常作为字符串读入,在处理之前必须将其转换为数字类型。可以使用列表生成式或map()函数转换所有列表元素的类型。
#将字符串列表转换为整数
items = ["1","2","3","4"]
[int(item) for item in items]
#: [1, 2, 3, 4]
#将字符串列表转换为浮点数
items = ["1","2","3","4"]
map(float, items)
#[1.0, 2.0, 3.0, 4.0]
嵌套list列表生成式
与嵌套循环的列表生成式不同,嵌套列表生成式是列表生成式中的列表生成式。初始表达式可以是任意表达式,包括另一个列表生成式。
#带嵌套循环的list列表生成式
[x + y for x in [1, 2, 3] for y in [3, 4, 5]]
# [4, 5, 6, 5, 6, 7, 6, 7, 8]
#嵌套list列表生成式
[[x + y for x in [1, 2, 3]] for y in [3, 4, 5]]
# [[4, 5, 6], [5, 6, 7], [6, 7, 8]]
这个嵌套的例子等价于:
l = []
for y in [3, 4, 5]:
temp = []
for x in [1, 2, 3]:
temp.append(x + y)
l.append(temp)
嵌套的列表生成式可以用来转置矩阵的一个例子:
matrix = [[1,2,3],
[4,5,6],
[7,8,9]]
[[row[i] for row in matrix] for i in range(len(matrix))]
#[[1, 4, 7], [2, 5, 8], [3, 6, 9]]
与嵌套的for循环一样,生成式可以嵌套的深度也没有限制
[[[i + j + k for k in 'cd'] for j in 'ab'] for i in '12']
#输出: [[['1ac', '1ad'], ['1bc', '1bd']], [['2ac', '2ad'], ['2bc', '2bd']]]
list生成式内同时迭代两个或多个列表
对于在列表生成式范围内同时迭代两个以上的列表,可以使用zip():
list_1 = [1, 2, 3 , 4]
list_2 = ['a', 'b', 'c', 'd']
list_3 = ['6', '7', '8', '9']
#两个 lists
[(i, j) for i, j in zip(list_1, list_2)]
# [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]
#三个 lists
[(i, j, k) for i, j, k in zip(list_1, list_2, list_3)]#
# [(1, 'a', '6'), (2, 'b', '7'), (3, 'c', '8'), (4, 'd', '9')]
# N个 lists........
禁止转载,违者必究!