zoukankan      html  css  js  c++  java
  • 【学习笔记】第四章 Python3核心技术与实践--列表与元组

    前面的课程,我们了解了Python 语言的学习方法,并且带你了解了 Python 必知的常用工具——Jupyter。接下来我们正式学习 Python 的具体知识。

    对于每一门编程语言来说,数据结构都是其根基。了解掌握 Python 的基本数据结构,对于学好这门语言至关重要。今天我们就一起来学习,Python 中最常见的两种数据结构:列表(list)和元组(tuple)。

    一、列表和元组基础

    首先,我们需要弄清楚最基本的概念,什么是列表和元组呢?

    实际上,列表和元组,都是一个可以放置任意数据类型的有序集合。

    在绝大多数编程语言中,集合的数据类型必须一致。不过,对于 Python 的列表和元组来说,并无此要求:

    l = [1, 2, 'hello', 'world'] # 列表中同时含有 int 和 string 类型的元素
    l
    [1, 2, 'hello', 'world']

    tup = ('jason', 22) # 元组中同时含有 int 和 string 类型的元素
    tup
    ('jason', 22)

    二、列表和元组的区别

    列表是动态的,长度大小不固定,可以随意地增加、删减或者改变元素(mutable)。
    而元组是静态的,长度大小固定,无法增加删减或者改变(immutable)。
    下面的例子中,我们分别创建了一个列表与元组。你可以看到,对于列表,我们可以很轻松地让其最后一个元素,由 4 变为 40;但是,如果你对元组采取相同的操作,Python 就会报错,原因就是元组是不可变的。

    l = [1, 2, 3, 4]
    l[3] = 40 # 和很多语言类似,python 中索引同样从 0 开始,l[3] 表示访问列表的第四个元素
    l
    [1, 2, 3, 40]

    tup = (1, 2, 3, 4)
    tup[3] = 40
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    TypeError: 'tuple' object does not support item assignment

    可是,如果你想对已有的元组做任何"改变",该怎么办呢?那就只能重新开辟一块内存,创建新的元组了。

    比如下面的例子,我们想增加一个元素 5 给元组,实际上就是创建了一个新的元组,然后把原来两个元组的值依次填充进去。

    而对于列表来说,由于其是动态的,我们只需简单地在列表末尾,加入对应元素就可以了。如下操作后,会修改原来列表中的元素,而不会创建新的列表。

    tup = (1, 2, 3, 4)
    new_tup = tup + (5, ) # 创建新的元组 new_tup,并依次填充原元组的值
    new _tup
    (1, 2, 3, 4, 5)

    l = [1, 2, 3, 4]
    l.append(5) # 添加元素 5 到原列表的末尾
    l
    [1, 2, 3, 4, 5]

    通过上面的例子,相信你肯定掌握了列表和元组的基本概念。接下来我们来看一些列表和元组的基本操作和注意事项。

    首先,和其他语言不同,Python 中的列表和元组都支持负数索引,-1 表示最后一个元素,-2 表示倒数第二个元素,以此类推。

    l = [1, 2, 3, 4]
    l[-1]
    4

    tup = (1, 2, 3, 4)
    tup[-1]
    4

    除了基本的初始化,索引外,列表和元组都支持切片操作:

    l = [1, 2, 3, 4]
    l[1:3] # 返回列表中索引从 1 到 2 的子列表
    [2, 3]

    tup = (1, 2, 3, 4)
    tup[1:3] # 返回元组中索引从 1 到 2 的子元组
    (2, 3)
    另外,列表和元组都可以随意嵌套:

    l = [[1, 2, 3], [4, 5]] # 列表的每一个元素也是一个列表

    tup = ((1, 2, 3), (4, 5, 6)) # 元组的每一个元素也是一元组

    当然,两者也可以通过 list() 和 tuple() 函数相互转换:

    list((1, 2, 3))
    [1, 2, 3]

    tuple([1, 2, 3])
    (1, 2, 3)

    最后,我们来看一些列表和元组常用的内置函数:

    l = [3, 2, 3, 7, 8, 1]
    l.count(3)
    2
    l.index(7)
    3
    l.reverse()
    l
    [1, 8, 7, 3, 2, 3]
    l.sort()
    l
    [1, 2, 3, 3, 7, 8]

    tup = (3, 2, 3, 7, 8, 1)
    tup.count(3)
    2
    tup.index(7)
    3
    list(reversed(tup))
    [1, 8, 7, 3, 2, 3]
    sorted(tup)
    [1, 2, 3, 3, 7, 8]

    三、几个函数的意义

    count(item) 表示统计列表 / 元组中 item 出现的次数。
    index(item) 表示返回列表 / 元组中 item 第一次出现的索引。
    list.reverse() 和 list.sort() 分别表示原地倒转列表和排序(注意,元组没有内置的这两个函数)。
    reversed() 和 sorted() 同样表示对列表 / 元组进行倒转和排序,但是会返回一个倒转后或者排好序的新的列表 / 元组。
    列表和元组存储方式的差异

    前面说了,列表和元组最重要的区别就是,列表是动态的、可变的,而元组是静态的、不可变的。这样的差异,势必会影响两者存储方式。我们可以来看下面的例子:

    l = [1, 2, 3]
    l.__sizeof__()
    64
    tup = (1, 2, 3)
    tup.__sizeof__()
    48

    你可以看到,对列表和元组,我们放置了相同的元素,但是元组的存储空间,却比列表要少 16 字节。这是为什么呢?

    事实上,由于列表是动态的,所以它需要存储指针,来指向对应的元素(上述例子中,对于 int 型,8 字节)。另外,由于列表可变,所以需要额外存储已经分配的长度大小(8 字节),这样才可以实时追踪列表空间的使用情况,当空间不足时,及时分配额外空间。

    l = []
    l.__sizeof__() // 空列表的存储空间为 40 字节
    40
    l.append(1)
    l.__sizeof__()
    72 // 加入了元素 1 之后,列表为其分配了可以存储 4 个元素的空间 (72 - 40)/8 = 4
    l.append(2)
    l.__sizeof__()
    72 // 由于之前分配了空间,所以加入元素 2,列表空间不变
    l.append(3)
    l.__sizeof__()
    72 // 同上
    l.append(4)
    l.__sizeof__()
    72 // 同上
    l.append(5)
    l.__sizeof__()
    104 // 加入元素 5 之后,列表的空间不足,所以又额外分配了可以存储 4 个元素的空间

    上面的例子,大概描述了列表空间分配的过程。我们可以看到,为了减小每次增加 / 删减操作时空间分配的开销,Python 每次分配空间时都会额外多分配一些,这样的机制(over-allocating)保证了其操作的高效性:增加 / 删除的时间复杂度均为 O(1)。

    但是对于元组,情况就不同了。元组长度大小固定,元素不可变,所以存储空间固定。

    看了前面的分析,你也许会觉得,这样的差异可以忽略不计。但是想象一下,如果列表和元组存储元素的个数是一亿,十亿甚至更大数量级时,你还能忽略这样的差异吗?

    四、列表和元组的性能

    通过学习列表和元组存储方式的差异,我们可以得出结论:元组要比列表更加轻量级一些,所以总体上来说,元组的性能速度要略优于列表。

    另外,Python 会在后台,对静态数据做一些资源缓存(resource caching)。通常来说,因为垃圾回收机制的存在,如果一些变量不被使用了,Python 就会回收它们所占用的内存,返还给操作系统,以便其他变量或其他应用使用。

    但是对于一些静态变量,比如元组,如果它不被使用并且占用空间不大时,Python 会暂时缓存这部分内存。这样,下次我们再创建同样大小的元组时,Python 就可以不用再向操作系统发出请求,去寻找内存,而是可以直接分配之前缓存的内存空间,这样就能大大加快程序的运行速度。

    下面的例子,是计算初始化一个相同元素的列表和元组分别所需的时间。我们可以看到,元组的初始化速度,要比列表快 5 倍。

    python3 -m timeit 'x=(1,2,3,4,5,6)'
    20000000 loops, best of 5: 9.97 nsec per loop
    python3 -m timeit 'x=[1,2,3,4,5,6]'
    5000000 loops, best of 5: 50.1 nsec per loop

    但如果是索引操作的话,两者的速度差别非常小,几乎可以忽略不计。

    python3 -m timeit -s 'x=[1,2,3,4,5,6]' 'y=x[3]'
    10000000 loops, best of 5: 22.2 nsec per loop
    python3 -m timeit -s 'x=(1,2,3,4,5,6)' 'y=x[3]'
    10000000 loops, best of 5: 21.9 nsec per loop

    当然,如果你想要增加、删减或者改变元素,那么列表显然更优。原因你现在肯定知道了,那就是对于元组,你必须得通过新建一个元组来完成。

    五、列表和元组的使用场景

    那么列表和元组到底用哪一个呢?根据上面所说的特性,我们具体情况具体分析。

    1. 如果存储的数据和数量不变,比如你有一个函数,需要返回的是一个地点的经纬度,然后直接传给前端渲染,那么肯定选用元组更合适。

    def get_location():
    .....
    return (longitude, latitude)

    2. 如果存储的数据或数量是可变的,比如社交平台上的一个日志功能,是统计一个用户在一周之内看了哪些用户的帖子,那么则用列表更合适。

    viewer_owner_id_list = [] # 里面的每个元素记录了这个 viewer 一周内看过的所有 owner 的 id
    records = queryDB(viewer_id) # 索引数据库,拿到某个 viewer 一周内的日志
    for record in records:
    viewer_owner_id_list.append(record.id)

    六、总结

    关于列表和元组,我们今天聊了很多,最后一起总结一下你必须掌握的内容。

    总的来说,列表和元组都是有序的,可以存储任意数据类型的集合,区别主要在于下面这两点。

    列表是动态的,长度可变,可以随意的增加、删减或改变元素。列表的存储空间略大于元组,性能略逊于元组。
    元组是静态的,长度大小固定,不可以对元素进行增加、删减或者改变操作。元组相对于列表更加轻量级,性能稍优。


    七、思考题

    1. 想创建一个空的列表,我们可以用下面的 A、B 两种方式,请问它们在效率上有什么区别吗?我们应该优先考虑使用哪种呢?可以说说你的理由。

    # 创建空列表
    # option A
    empty_list = list()

    # option B
    empty_list = []

    2. 你在平时的学习工作中,是在什么场景下使用列表或者元组呢?欢迎留言和我分享。

    思考题答案见下一章

  • 相关阅读:
    父子进程 signal 出现 Interrupted system call 问题
    一个测试文章
    《淘宝客户端 for Android》项目实战 html webkit android css3
    Django 中的 ForeignKey ContentType GenericForeignKey 对应的数据库结构
    coreseek 出现段错误和Unigram dictionary load Error 新情况(Gentoo)
    一个 PAM dbus 例子
    漫画统计学 T分数
    解决 paramiko 安装问题 Unable to find vcvarsall.bat
    20141202
    js
  • 原文地址:https://www.cnblogs.com/tianyu2018/p/10964804.html
Copyright © 2011-2022 走看看