python 序列的高级用法

2019-04-13 15:11:25   最后更新: 2019-04-13 15:11:25   访问数量:121




上一篇文章中,我们介绍了三个最常用、最基本的序列类型

python 序列与深浅拷贝

本文我们详细介绍一下 Python 中现有的全部序列类型以及更为高级的用法

 

 

 

按照存储内容

存储引用的序列

  • list
  • tuple
  • colletions.deque

 

以上这些序列中存储的是对象的引用,因此他们不关心所引用的存储对象的类型,也就是说,在一个序列中可以放入不同类型的对象

 

存储对象的值

  • str
  • bytes
  • bytearray
  • memoryview
  • array.array

 

上述这些序列类型存储的是对象的值,他们是一段连续的存储空间,只能容纳一种类型

 

按照存储内容是否可改变

可变序列

  • list
  • bytearray
  • array.array
  • collections.deque
  • memoryview

 

不可变序列

  • tuple
  • str
  • bytes

 

介绍

下面的代码把一个字符串转换成 unicode 码存储在 list 中并输出:

>>> symbols = '$¢£¥€¤' >>> codes = [] >>> for symbol in symbols: ... codes.append(ord(symbol)) ... >>> codes [36, 162, 163, 165, 8364, 164]

 

 

下面我们将他改成列表推导的形式:

>>> symbols = '$¢£¥€¤' >>> codes = [ord(symbol) for symbol in symbols] >>> codes [36, 162, 163, 165, 8364, 164]

 

 

显然,列表推导的方法大大简化了上述代码,逻辑更加清晰简练,他可以十分简洁的实现可迭代类型的元素过滤或加工,并创建出一个新列表

 

多重循环

列表推导中我们是可以放入多个循环的,例如下面这个生成笛卡尔积的例子:

>>> colors = ['black', 'white'] >>> sizes = ['S', 'M', 'L'] >>> tshirts = [(color, size) for color in colors for size in sizes] >>> tshirts [('black', 'S'), ('black', 'M'), ('black', 'L'), ('white', 'S'), ('white', 'M'), ('white', 'L')]

 

 

注意

但需要注意的是,不要滥用列表推导:

  1. 只把创建新列表的工作交给列表推导
  2. 如果列表推导超过两行,不如改为使用 for 循环

 

filter 与 map 结合 lambda 表达式也可以做到和列表推导相同的功能,但可读性大为下降

下面的例子将 Unicode 值大于 127 的字符对应的 Unicode 值加入列表中:

>>> symbols = '$¢£¥€¤' >>> beyond_ascii = [ord(s) for s in symbols if ord(s) > 127] >>> beyond_ascii [162, 163, 165, 8364, 164] >>> beyond_ascii = list(filter(lambda c: c > 127, map(ord, symbols))) >>> beyond_ascii [162, 163, 165, 8364, 164]

 

 

上面所有例子中,我们都只生成了列表,如果我们要生成其他类型的序列,列表推导就不适用了,此时生成器表达式成为了更好的选择

简单地说,把列表推导的方括号变成圆括号就是生成器表达式,但在用法上,生成器表达式通常用于生成序列作为方法的参数

下面的例子用生成器表达式计算了一组笛卡尔积:

>>> colors = ['black', 'white'] >>> sizes = ['S', 'M', 'L'] >>> for tshirt in ('%s %s' % (c, s) for c in colors for s in sizes): ... print(tshirt)

 

 

生成器与列表推导存在本质上的不同,生成器实际上是一种惰性实现,他不会一次产生整个序列,而是每次生成一个元素,这与迭代器的原理非常类似,如果列表元素非常多,使用列表生成器可以在很大程度上节约内存的开销

 

上一篇文章中,我们介绍了元组作为不可变列表的用法,但一个同样重要的用法是把元组用作信息的记录

>>> city, year, pop, chg, area = ('Tokyo', 2003, 32450, 0.66, 8014)

 

 

可以看到,上面的例子中只用一行代码,就让元组中的每个元素都被赋值给不同的变量,这个过程就被称为元组拆包

 

通过元组拆包实现变量交换

下面就是一个通过元组拆包实现的十分优雅的变量交换操作:

>>> b, a = a, b

 

 

除了给变量赋值,只要可迭代对象的元素数与元组中元素数量一致,任何可迭代对象都可以用元组拆包来赋值

 

可迭代对象的拆包

可以用 * 运算符将任何一个可迭代对象拆包作为方法的参数:

>>> divmod(20, 8) (2, 4) >>> t = (20, 8) >>> divmod(*t) (2, 4)

 

 

不确定拆分结果的获取

Python 允许被拆包赋值的一系列变量中最多存在一个以 * 开始的变量,他用来接收所有拆包赋值后剩下的变量

*args 用来获取不确定参数是最经典的写法了

>>> a, b, *rest = range(5) >>> a, b, rest (0, 1, [2, 3, 4])

 

 

元组拆包的嵌套

元组拆包是可以嵌套的,只要接受元组嵌套结构符合表达式本身的嵌套结构,Python 就可以做出正确的处理

 

具名元组 -- collections.namedtuple

具名元组就是带有名字和字段名的元组,他用元组模拟了一个简易的类

我们通过 collections.namedtuple 方法就可以构建一个具名元组:

>>> from collections import namedtuple >>> City = namedtuple('City', 'name country population coordinates') >>> tokyo = City('Tokyo', 'JP', 36.933, (35.689722, 139.691667)) >>> tokyo City(name='Tokyo', country='JP', population=36.933, coordinates=(35.689722, 139.691667))

 

 

本质上,具名元组仍然是元组用于记录元素的一种用法

 

具名元组的属性和方法

除了所有元组具有的属性和方法,具名元组还具有下面三个有用的属性和方法

  • 【_fields】 -- 类属性,包含具名元组所有字段名称的元组
  • 【_make()】 -- 通过接受一个可迭代对象生成类实例,如 City._make(*delphi_data)
  • 【_asdict()】 -- 把具名元组以 collections.OrderedDict 类型返回,可以用于友好的展示

 

序列类型有很多,虽然大部分人在大部分时间都喜欢使用 list,但要知道某些时候你还有更好的选择:

  • list -- 最常用的序列类型,使用方便,尤其在元素的添加、随机读取和遍历等方面
  • tuple -- 元组,不可变的序列类型
  • set -- 不重复的元素集合,对包含操作(如检查一个元素是否在集合中)有着特殊优化,这类操作的效率会非常高
  • array.array -- 对于 float 对象存储的是字节码表述,存储效率比 list 高得多,如果元素是大量的数字,他将会是优于 list 的选择
  • collections.deque -- 可以非常方便的实现序列两端元素的进出操作,对于栈和队列数据结构实现了原生的支持

 

欢迎关注微信公众号,以技术为主,涉及历史、人文等多领域的学习与感悟,每周三到七篇推文,全部原创,只有干货没有鸡汤

 

 

《流畅的 python》

 






python      array      元组      容器      list      序列      tuple      列表     


京ICP备15018585号