Python中的序列类型(一)

内置序列类型概览

容器序列

list、 tuple 和 collections.deque 这些序列能存放不同类型的数据。

扁平序列

bytes、str、memoryview、bytearray 和 array.array,这些序列只能容纳一种类型

容器序列存放的是它们所包含的任意类型的对象的引用,而扁平序列里存放的是值而不
是引用。

换句话说,扁平序列其实是一段连续的内存空间。由此可见扁平序列其实更加紧
凑,但是它里面只能存放诸如字符、字节和数值这种基础类型。

序列还能按照是否能被修改分类

可变序列

List、bytearray、array.array、collections.array memoryview

不可变序列

tuple、str 和 bytes

[图片上传失败...(image-21d861-1544191455627)]

上图列举了 collections.abc 中的几个类,(超类在左,箭头从子类指向超类,斜体名称代表抽象类和抽象方法)

列表推导和生成器表达式

列表推导

python会忽略 []、{}、() 中的换行,所以在其中可以省略续行符 \

列表推导的局部作用域:

>>> x = 'abc'
>>> dummy = [ord(x) for x in x]
>>> dummy
[97, 98, 99]

表达式内部的赋值和变量只在局部起作用,并不会影响上下文的同名变量。生成器表达式、集合推导、字典推导也有一样的效果。

使用列表推导计算笛卡尔积:

用列表推导式可以生成两个或两个以上列表的笛卡儿积,笛卡儿积是一个列表,列表中的元素是输入的可迭代类型的元素对构成的元组。

>>> cards = [(suit, rank) for suit in suits for rank in ranks]
>>> cards
[('spades', 1), ('spades', 2), ('spades', 3), ('diamonds', 1), ('diamonds', 2), ('diamonds', 3), ('clubs', 1), ('clubs', 2), ('clubs', 3), ('hearts', 1), ('hearts', 2), ('hearts', 3)]
>>> cards = [(suit, rank) for rank in ranks for suit in suits]
>>> cards
[('spades', 1), ('diamonds', 1), ('clubs', 1), ('hearts', 1), ('spades', 2), ('diamonds', 2), ('clubs', 2), ('hearts', 2), ('spades', 3), ('diamonds', 3), ('clubs', 3), ('hearts', 3)]

从上面可知,生成的笛卡儿积列表内元素的顺序与两个从句的先后顺序有关。

生成器表达式

生成器表达式语法和列表推导差不多,只是方括号换成了圆括号而已。

而生成器是更好的选择,它不会直接生成一个列表,而是生成一个 generator,可以通过不断迭代获取值,占用内存空间小

如果生成器表达式是调用函数的唯一参数,可以省略外面的括号。

当要计算两个各有1000各元素的列表的笛卡儿积时,生成器表达式的优势就体现出来了,即逐个产生元素,不会产生一个长度为1000*1000的列表。

切片

哪些类型支持切片:list、tuple、字符串

切片和区间操作会会略区间范围内的最后一个元素,这是Python和C等语言的风格,有以下三点好处:

  1. 即使只包含最后一个位置信息,我们也可以清楚地知道切片和区间里有几个元素,如range(3) 和 my_list[:3]都返回3个元素

  2. 当起止信息均可见时,可以快速算出区间或切片所包含的元素个数,即:end - start

  3. 可以用一个位置信息将整个序列分割为互不重叠的两部分,只要写成mylist[:x] 和 my_list[3:]

>>> l = range(10)
>>> list(l[:4])
[0, 1, 2, 3]
>>> list(l[4:])
[4, 5, 6, 7, 8, 9]

seq[start:end:step] 这种带步长的切片操作只能作为索引或下标用在[]来返回一个对象,它会调用 seq.__getitem__(slice[start:end:step])

给切片赋值:

当等号左边是一个切片对象时,等号右边必须是一个可迭代对象,即使只有一个元素,也必须以可迭代对象的格式写出:


>>> l = list(range(10))
>>> l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> l[1:3] = 20
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: can only assign an iterable
>>> l[1:3] = [20]
>>> l
[0, 20, 3, 4, 5, 6, 7, 8, 9]

切片对象也支持 del操作:

>>> l
[0, 20, 3, 4, 5, 6, 7, 8, 9]
>>> del l[1:4]
>>> l
[0, 5, 6, 7, 8, 9]

* 和 + 在序列中的应用

序列对象支持拼接操作 +。如果想把一个序列复制几份再拼接起来,可以使用 * 操作。


>>> 'fe'*3
'fefefe'
>>> l=list(range(3))
>>> l*3
[0, 1, 2, 0, 1, 2, 0, 1, 2]
>>> l
[0, 1, 2]

对序列使用 * 操作,不会改变原有的操作对象,而是新建一个序列对象,这也是python的风格之一。

建立由列表组成的列表

[['_'] * 3 for x in range(3)] 与 [['_'] * 3]*3的区别

如果我想建立一个由3个包含3个元素的列表组成的列表:


# 子列表
>>> m = [['_']*3]*3
>>> m
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]

创建成功。

但是,用[['_']*3]*3能否可行呢?


>>> [['_']*3]*3
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]

看似可行,但是当我们尝试更改其中一个列表的元素时:


>>> m[1][2] = 'x'
>>> m
[['_', '_', 'x'], ['_', '_', 'x'], ['_', '_', 'x']]

可以看出,每个子列表都被更改了,猜测一下原因,应该是创建列表时引用了同一个子列表对象。

利用 python 可视化工具查看可以一目了然:

第一种方法:

第二种方法:

事实上,前面的正确方法与下面的操作类似:

board = []
for x in range(3):
    row = ['_']*3
    board.append(row)

>>> board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> board[1][2] = 'x'
>>> board
[['_', '_', '_'], ['_', '_', 'x'], ['_', '_', '_']]

而第二种方法所犯错误与下面等同:

row = ['_']*3
board = []
for x in range(3):
    board.append(row)

可以看出,错误方法引用了相同的 row 对象

>>> board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> board[1][2] = 'x'
>>> board
[['_', '_', 'x'], ['_', '_', 'x'], ['_', '_', 'x']]

*= 和 += 之于可变对象和不可变对象

*= 和 += 称为就地乘法和就地加法,顾名思义,就是在这个对象上直接进行加乘操作。

但是,对于可变对象和不可变对象,它们作用在其上是不同的。

对于可变对象(如bytearray,array.array,list)来说,*= 和 += 会调用对象的 __iadd____imul__ 方法,对象就地改动:

>>> a = list(range(4))
>>> a
[0, 1, 2, 3]
>>> id(a)
2372785454984
>>> a += [4]
>>> a
[0, 1, 2, 3, 4]
>>> id(a)
2372785454984

可以看出,就地加法后,变量a所指向的还是原对象。

对于不可变对象(如 tuple、bytes 等)来说,由于其没有 __iadd____imul__ 方法,当 *= 和 += 时,会调用它的 __add____mul__ 方法,但这时 a+=b 就相当于 a = a + b,会先计算 a + b,得到一个新的对象,然后把它赋值给 a,也就是此时的变量 a 指向的已经是一个新的对象了。

str 对象是个例外,由于其经常要进行拼接,所以CPython对其进行了优化,在创建时就会为留出额外的可扩展空间,因此进行增量操作时,并不会出现复制原有内容到新位置这类操作。

>>> a = tuple(range(4))
>>> a
(0, 1, 2, 3)
>>> id(a)
2372785290200
>>> a += (4,)
>>> a
(0, 1, 2, 3, 4)
>>> id(a)
2372779229608

看到,如上所说,a 已经指向了一个新的元组。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,919评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,567评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,316评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,294评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,318评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,245评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,120评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,964评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,376评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,592评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,764评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,460评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,070评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,697评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,846评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,819评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,665评论 2 354

推荐阅读更多精彩内容