Python: 常用的数据结构
不可变/可变类型
回顾一下不可变、可变类型的区别
展现给开发者的:
内置不可变类型不提供任何
set类型的API,最多仅提供get类型的方法(例如点操作符、[]切片语法糖等)(且在用户试图修改这些方法的返回值时抛出TypeError或AttributeError),用户只能通过赋值修改实例的引用来实现修改对象的功能 用户既不能修改内容,也不能在类外绑定新的实例属性 需要再次强调,不可变类型和C++ const&引用类型不是一种东西,更像C++ const*:用户完全可以修改其指向,只是不能修改其指向的内容很自然的是,因为所有对象均为引用(包括形参),因此传递参数时: 若为不可变类型参数,则修改该参数不会影响实际参数(因为用户只能修改形参的指向) 若为可变类型参数,则修改其内容将影响实际参数(形参、实参指向相同的数据)、通过赋值修改形参的引用不会影响实际参数(修改形参的指向,未修改实参的引用及其指向的数据)
用户定义的类均为可变类型,可以模仿不可变类型的行为,有多种灵活的方法: 首先是类属性这种所有实例共用的数据,在初始化时就应设置成不可变类型 定义不可变实例属性:
- 用
@property将一个实例方法转换为实例属性,当用户访问该实例属性时,相当于调用该实例方法;使该实例方法始终返回一个不可变类型实例,就能实现使该实例属性无法修改 - 最常用的保底机制:直接重载
__setattr__()进行检查,拦截被修改/绑定的属性,可由用户决定是否抛出异常 也可以防止用户在类外绑定实例属性
不允许用户在类外绑定实例属性:除了上述方法外,还可定义类属性
__slots__(一个由字符串组成的元组),表示允许用户绑定的实例属性名实例原本通过字典
__dict__维护其实例属性,但定义这个类属性后,实例不再拥有__dict__,而是一个固定长度的数组这个类属性不允许包含其它类属性名,否则会抛出
ValueError配合上在用户通过实例修改类属性时,解释器优先解释为用户想要创建和类属性同名的实例属性这个特性,这种操作必然会报错,虽然原因不同:

(1)试图创建不在
__slots__中的新的实例属性(2)试图通过实例修改类属性 还是一句话,尽量不要通过实例访问类属性,如果手贱,尽量只进行只读操作这个类属性不对子类生效
- 用
内部实现细节:
- 解释器会为内置不可变类型的对象维护缓存,使内容相同的对象只指向一份实际内存空间
- 当用户调用赋值符修改属性时,实际是调用内置的
__setattr__(),这也是上述保底机制的原理 - 当用户调用赋值符修改对象引用时,维护原/新数据的引用计数,通过
GC维护内存
序列公共API
- 序列的赋值:
- 内置序列类型,即有序的数据集合,包括元组(不可变)、列表(可变)、字符串(不可变)、
range()(不可变) - 运算符语法糖:
seq1 + seq2:拼接序列,返回新实例seq * n:拼接n个相同序列,返回新实例val in/not in seq:判断值是否在序列中,返回布尔值
len():返回长度max()/min():返回序列中最大/最小值,要求所有元素的类型相同(除非是python允许的隐式类型转换)range(start, end, step=1):创建从start到end(包含)的步长为step的range类(整数序列),step默认为1,step允许为负数,不允许为零 和切片的功能不同,不能用于截取序列,其用法是快速创建整数数组方便产生for循环索引/数据分析slice(start, end, step=1):参数用法与range()类似(end不包含),但产生的序列不能访问,其正确用法是通过[]作用于其它序列上,截取子序列[]语法糖:[x]:访问下标为x的元素[s:e]:等同于[slice(s, e)],s、e允许为空,此时代表取到头/尾[s:e:i]:等同于[slice(s, e, i)],同样i允许为负数
enumerate(seq, start=1):返回由元组构成的序列,其中元组的第一项为整数序列,从start(默认为1)开始,第二项为序列中的内容(从下标0代表的元素开始) 实际上,所有可遍历