列表 list 和元组 tuple 基本使用

列表


  • 可变
  • [] 表示
  • 线性的数据结构
  • 列表内元素有顺序,可用索引
  • 列表内元素可以是任意对象(数字,字符串,对象,列表等)

list 定义及初始化

  • list()
  • list(iterable)
  • 不能一开始就定义大小,区别 c 中的数组
lst = list()
lst = []
lst = [1,'a','ab',[1,2],(1,),{1,2},{'a':1}]
lst = list[range(4)]

list 索引访问

  • 索引:正索引 0 开始,负索引 - 1 开始,不可越界,否则抛出 IndexError 异常
  • list[index]

list 查询函数

  1. index(value[,start][,stop])
  • 在 start 到 stop 区间内查找 value 值
  • 匹配到第一个立即返回索引
  • 匹配不到,抛出 ValueError 异常
  • 时间复杂度 O(n)
  1. count(value)
  • 返回列表中匹配 value 的次数
  • 时间复杂度 O(n)
  1. len()
  • 返回列表长度

list 元素修改

  1. 索引访问修改
  • list[index] = value
  • 索引不要越界

list 增加、插入元素

  1. append(object)
    • 返回值为 None
    • 无新的列表产生,修改原列表
    • 时间复杂度 O(1)
  2. insert(index,object)
  • 在指定索引处插入元素 object
  • 返回 None
  • 无新的列表产生,修改原列表
  • 时间复杂度 O(n)
  • 索引可超界:超上界,尾部追加,超下界,首部追加
  1. extend(iterable)
  • 将可迭代对象追加进来,返回 None
  • 无新的列表产生,修改原列表
  1. +
  • 连接两个 list 生成新的 list,原 list 不变
  • 本质是调用了魔术方法__add__
  1. *
  • 重复操作,将本列表元素重复 n 次,返回新的列表
# 比较
x = [[1,2]]*2
print(x)
x[0][0] = 100
print(x)
print('*'*20)
y = [1,2]*2
y[0] = 100
print(y)
--------------------------------
[[1, 2], [1, 2]]
[[100, 2], [100, 2]]
********************
[100, 2, 1, 2]

注意这种嵌套的 list(包含子对象)
结论:浅拷贝问题

list 删除元素

  1. remove(value)
  • 返回值为 None
  • 从左到右查找到第一个匹配的 value 值并移除
  • 修改原 list
  1. pop(index)
  • 不指定 index,从尾部弹出一个元素
  • 指定 index,从索引处弹出一个元素,索引越界抛出 IndexError 异常
  • 返回值为 pop 出的对象,且原 list 被修改
  1. clear()
  • 清除所有元素,原 list 为空列表
  • 返回值为 None

list 其他操作

  1. reverse()
  • 将列表元素反转
  • 返回 None
  • 修改原 list
  1. sort(key=None,reverse=False)
  • 对原 list 进行排序,修改原列表,默认为升序
  • 返回值为 None,修改原 list
  • key 是一个函数
  1. in 和 not in
  • 返回 bool 值

赋值、深拷贝、浅拷贝


# 赋值
a = [1,2,3,[4,5]]
b = a
a[0] = 100
a[3][0] = 100
print((a,b))
---------------------------------
([100, 2, 3, [100, 5]], [100, 2, 3, [100, 5]]) # 相互影响

a = [1, 2, 3]
b = a
b.append(4)
print(a)  # 输出 [1, 2, 3, 4]

# 浅拷贝1
a = [1,2,3,[4,5]]
b = a.copy() # b = list(a) 也是浅拷贝 或import copy后使用b = copy.copy(a)
a[0] = 100
print((a,b))
a[3][0] = 100
print((a,b))
----------------------------------
([100, 2, 3, [4, 5]], [1, 2, 3, [4, 5]])
([100, 2, 3, [100, 5]], [1, 2, 3, [100, 5]]) # 仅子对象相互影响

# 深拷贝
import copy
a = [1,2,3,[4,5]]
b = copy.deepcopy(a)
a[0] = 100
print((a,b))
a[3][0] = 100
print((a,b))
----------------------------------
([100, 2, 3, [4, 5]], [1, 2, 3, [4, 5]]) # 不相互影响
([100, 2, 3, [100, 5]], [1, 2, 3, [4, 5]]) # 不相互影响

结论:
变量的赋值、浅拷贝和深拷贝都是用来创建对象的副本

  1. 赋值操作
    赋值操作是将一个对象的引用赋值给一个变量,这个变量与原对象共享同一个内存地址。这意味着如果更改变量的值,也会更改原始对象的值
  2. 浅拷贝
    浅拷贝是创建一个新对象,该对象与原始对象的内容相同,但是该对象的一些或全部元素是原始对象的引用。这意味着如果更改了浅拷贝对象中的引用对象,那么原始对象也会受到影响
  3. 深拷贝
    深拷贝是创建一个完全独立的新对象,它与原始对象的内容相同,但该对象中的所有元素都是原始对象的副本,而不是原始对象的引用。这意味着如果更改深拷贝对象中的引用对象,原始对象不会受到影响

需要注意的是,深拷贝可能会比浅拷贝更耗费计算资源,因为它需要递归地遍历整个对象,创建对象的完全独立副本。因此,在处理大型对象或嵌套层数较深的对象时,深拷贝可能会更加耗时

元组tuple


  • 有序的元素组合
  • () 表示
  • 不可变 对象

tuple 定义及初始化

  • t = tuple()
  • t = ()
  • t = tuple(iterable) 如 tuple(range(3))
  • t = (1,2,3)
  • t = (1,)
  • t = (1,)*5

tuple 元素访问

  • tuple[index]

tuple 查询

  • index(value[,start,[stop]])
    • 匹配到第一个立即返回索引
    • 匹配不到,ValueError 异常
    • 时间复杂度 O(n)
  • count(value)
    • 返回匹配 value 的次数
    • 时间复杂度 O(n)
  • len(tuple)

tuple 其他

  • 元组是只读的,增改删方法都没有

命名元组 namedtuple

  • 查看官方文档实例
  • collections.namedtuple(typename, field_names, *, verbose=False, rename=False, module=None)
>>> # Basic example
>>> Point = namedtuple('Point', ['x', 'y'])
>>> p = Point(11, y=22)     # instantiate with positional or keyword arguments
>>> p[0] + p[1]             # indexable like the plain tuple (11, 22)
33
>>> x, y = p                # unpack like a regular tuple
>>> x, y
(11, 22)
>>> p.x + p.y               # fields also accessible by name
33
>>> p                       # readable __repr__ with a name=value style
Point(x=11, y=22)

切片


  • 通过索引区间访问线性结构的一段数据
  • [start:stop] 包含 start,不包含 stop,即 [start,stop)
  • [:] 表示取全部
  • [start:stop:step],step 为步长,与 start:stop 同向,否则返回空序列

列表和元组存储方式比较


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

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

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

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

列表和元组的选择


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

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

应用场景:

  1. 如果存储的数据和数量不变,比如你有一个函数,需要返回的是一个地点的经纬度,然后直接传给前端渲染,那么肯定选用元组更合适
  2. 如果存储的数据或数量是可变的,比如社交平台上的一个日志功能,是统计一个用户在一周之内看了哪些用户的帖子,那么则用列表更合适

参考


  • magedu