美文网首页Python笔记
Python中的序列类型(一)

Python中的序列类型(一)

作者: SimonJoe246 | 来源:发表于2018-12-07 22:06 被阅读0次

    内置序列类型概览

    容器序列

    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 已经指向了一个新的元组。

    相关文章

      网友评论

        本文标题:Python中的序列类型(一)

        本文链接:https://www.haomeiwen.com/subject/jszxhqtx.html