Python成为专业人士笔记–comprehension生成式

“专业人士笔记”系列目录:

创帆云: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
      

其计算过程可以概括为 :

  1. 遍历范围1000以内的元素。
  2. 连接所有需要的if条件。
  3. 使用1作为表达式,为每个满足条件的项返回1
  4. 将所有的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........        
      

禁止转载,违者必究!

猜你喜欢

转载自blog.csdn.net/oSuiYing12/article/details/106211772
今日推荐