Python 列表推导和生成器表达式

列表推导和生成器表达式


列表推导是构建列表的一种方式,生成器表达式可以用来创建其他任何类型的序列。

列表推导


列表,可以通过多种方式构建:

  • . 使用一对方括号表示空列表:[]
  • . 使用方括号,里面的项用逗号隔开:[a],[a, b, c]
  • . 使用类型构造器:list() 或者 list(iterable)
  • . 使用列表推导:[x for x in iterable]

以上都是生成列表的形式。其中,列表推导更具有可读性。

列表推导和可读性

通过下面两个示例,对比使用方括号和列表推导:

示例 1:将字符串变为 Unicode 码位的列表

>>> letters = "abcdefg"
>>> codes = []
>>> for letter in letters:
...     codes.append(ord(letter))
...
>>> codes
[97, 98, 99, 100, 101, 102, 103]

示例 2:将字符串变为 Unicode 码位的另一种写法

>>> letters = "abcdefg"
>>> codes = [ord(letter) for letter in letters]
>>> codes
[97, 98, 99, 100, 101, 102, 103]

两者想比较下,列表推导更具有可读性。当然,列表推导乱用也会有副作用。通常的原则是,用列表推导创建新列表的时候,尽可能简短。若列表推导超过两行,可考虑使用 for 循环创建。但并没有硬性要求,选择权在使用者手上。

变量泄露

在 Python 2 中,列表推导存在变量泄露问题。但在 Python 3 中,这个问题将不会再出现。

Python 2 列表推导示例:

Python 2.7.12 (default, Oct  8 2019, 14:14:10) 
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> x = "this word"
>>> dummy = [x for x in "abc"]
>>> x
'c'

从结果可见,x 的值被取代了。

这是 Python 3 中的代码:

Python 3.6.5 |Anaconda, Inc.| (default, Apr 29 2018, 16:14:56) 
[GCC 7.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> x = "this word"
>>> dummy = [x for x in "abc"]
>>> x
'this word'
>>> dummy
['a', 'b', 'c']

x 的值没有发生改变,dummy 也创建成功。

过滤元素

这里尝试同 filter()map() 结合比较。

使用列表推导和 map/filter 结合创建同样的列表

>>> letters = "abcdefg"
>>> match_portion = [ord(letter) for letter in letters if ord(letter) > 100]
>>> match_portion
[101, 102, 103]

>>> match_portion = list(filter(lambda x: x > 100, map(ord, letters)))
>>> match_portion
[101, 102, 103]

这是两者各自的实现方式

In [4]: %timeit [ord(letter) for letter in letters if ord(letter) > 100]
764 ns ± 4.36 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [5]: %timeit list(filter(lambda x: x > 100, map(ord, letters)))
1.37 µs ± 5.37 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

这里是尝试比较两者的运行速度,可以看出, map/filter 组合比列表推导要慢些。

map() 函数接受两个参数,一个是函数,一个是 Iterablemap 将传入的函数依次作用到序列的每个元素,并把结果作为新的 Iterator 返回。

filter() 函数也接受一个函数和一个序列。和 map() 不同,filter() 把传入的函数依次作用于每个元素,然后依据返回值是 True 还是 False 决定保留还是丢弃该元素。

mapfilter 更详细的讨论,会在后面另开篇幅进行阐述。

生成器表达式


虽然列表推导能够用来初始化元组,数组或者其他序列类型,但是生成器表达式是更好的选择。因为生成器表达式遵守迭代器协议,是逐个生产出元素,而不是创建一个完整的列表,然后把列表传递到某个构造函数中。两者比较,显然是生成器表达式更节约内存。

生成器表达式的语法与列表推导类似,只是将方括号换成圆括号。

示例用生成器表达式初始化元组

>>> letters = "abcdefg"
>>> tuple(ord(letter) for letter in letters)
(97, 98, 99, 100, 101, 102, 103)

注意:如果生成器表达式是一个函数调用过程中的唯一参数,那么不需要额外在用括号将它们围起来。

>>> tuple(ord(letter) for letter in letters)
(97, 98, 99, 100, 101, 102, 103)

>>> tuple((ord(letter) for letter in letters))
(97, 98, 99, 100, 101, 102, 103)

效果相同,若生成器表达式是唯一参数,不需要额外括号。

尝试使用生成器表达式实现笛卡尔积,示例如下:

>>> letters = ['a', 'b', 'c', 'd', 'e', 'f']
>>> nums = ['1', '2', '3']

>>> for le_num in ('{} {}'.format(le, num) for le in letters for num in nums):
...     print(le_num)
... 
a 1
a 2
a 3
b 1
b 2
b 3
c 1
c 2
...

在这里,生成器表达式会在每次 for 循环运行的时候才产生一个组合,所以内存不会留下组合的列表。若两个列表元素数量非常多,生成器表达式在这里就能大大减少 for 运行的开销。

此处只是简单介绍生成器如何初始化列表外的序列,以及避免额外的内存占用。详细的生成器工作原理,后续同样会另开篇幅进行阐述。

参考资料


来源

  1. David M. Beazley;Brian K. Jones.Python Cookbook, 3rd Edtioni.O’Reilly Media.2013.
  2. Luciano Ramalho.Fluent Python.O’Reilly Media.2015
  3. “4. Built-in Types”.docs.python.org.Retrieved 14 January 2020
  4. 廖雪峰.“Python 教程”.liaoxuefeng.com.[2020-01-17].

以上就是本篇的主要内容


欢迎关注『书所集录』公众号
发布了61 篇原创文章 · 获赞 21 · 访问量 8191

猜你喜欢

转载自blog.csdn.net/weixin_45642918/article/details/104021148
今日推荐