可变和不可变
不可变类型:数字、字符串、元组、frozenset
可变类型:列表、字典、集合
这里的可变和不可变是指内存中的那块内容,是否可以被改变。如果是不可变类型,在对对象操作时必须重写开辟一块新内存(因为老旧区域不可变)。如果时可变类型,对对象操作时,不需要在其他地方申请内存
深拷贝与浅拷贝
深拷贝
深拷贝就是将一个对象拷贝到另一个对象中,这意味这你对一个对象的拷贝做出改变时,不会影响原对象。两者之间互不影响。
你抓鲁迅和我周树人有什么关系?
而浅拷贝是将一个对象的引用拷贝到另一个对象上,所有我们在拷贝中改动,会影响到原对象
切片操作时浅拷贝
工厂函数时浅拷贝: lst1 = list(lst)
浅拷贝要分三种情况进行讨论
- 拷贝不可变对象:只是增加一个指向原对象的引用,改变会互相影响
>>> a = (1, 2, [3, 4])>>> b = copy.copy(a)>>> b... (1, 2, [3, 4])# 改变一方,另一方也改变>>> b[2].append(5)>>> a... (1, 2, [3, 4, 5])
- 拷贝可变类型(一层结构):产生新的对象,开辟新的内存空间,改变互不影响
>>> import copy>>> a = [1, 2, 3]>>> b = copy.copy(a)>>> b... [1, 2, 3]# 查看两者的内存地址,不同,开辟了新的内存空间>>> id(b)... 1833997595272>>> id(a)... 1833997595080>>> a is b... False# 改变了一方,另一方关我卵事a = [1, 2, 3] b = [1, 2, 3]>>> b.append(4)>>> a... [1, 2, 3]>>> a.append(5)>>> b... [1, 2, 3, 4]
- 拷贝可变类型(多层结构):产生新的对象,开辟新的内存空间,不改变包含的子对象则互不影响,改变包换的子对象则相互影响
>>> import copy>>> a = [1, 2, [3, 4]]>>> b = copy.copy(a)>>> b... [1, 2, [3, 4]]# 查看两者的内存地址,不同,开辟了新的内存空间>>> id(b)1833997596488>>> id(a)1833997596424>>> a is b... False# 1.没有对包含的子对象进行修改,另一方关我卵事a = [1, 2, [3, 4]] b = [1, 2, [3, 4]]>>> b.append(5)>>> a... [1, 2, [3, 4]]>>> a.append(6)>>> b... [1, 2, [3, 4], 5]# 2.对包含的子对象进行修改,另一方也随之改变a = [1, 2, [3, 4]] b = [1, 2, [3, 4]]>>> b[2].append(5)>>> a... [1, 2, [3, 4, 5]]>>> a[2].append(6)>>> b... [1, 2, [3, 4, 5, 6]]
列表的底层实现
实现细节 python中的列表的英文名是list,因此很容易和其它语言(C++, Java等)标准库中常见的链表混淆。事实上CPython的列表根本不是列表(可能换成英文理解起来容易些:python中的list不是list)。在CPython中,列表被实现为长度可变的数组。
从细节上看,python中的列表是由其他对象的引用组成的连续数据。指向这个数组的指针及长度被保存在一个列表头结构中。也就是说每次添加或删除一个元素时,由引用组成的数据需要重写标记标大小。但在python中创建一个列表时,采用了指数过分配,所有并不是每次操作都需要改变数组的大小,因为这个原因,添加或取出元素的平坦复杂度极低。
通俗点说就是当列表被创建时它会申请一个比自身要大的内存,当自身的内存与申请的内存相同后,它会在申请一个内存然后将之前所有的数据都拷贝进去。
但在使用insert方法在任意位置插入一个元素它的时间复杂度为O(n),使用pop或remove删除一个元素复杂度也是O(n)
列表推导
要习惯用列表推导,因为这更加高效和简短,涉及的语法元素少。在大型的程序中,这意味着更少的错误,代码也更容易阅读。
>>>[i for i in range(10) if i % 2 == 0]
[0, 2, 4, 6, 8]
1.使用enumerate.在循环使用序列时,这个内置函数可以方便的获取其索引:
>>> for i, element in enumerate(["one","two","three"]):
... print(i, element)
...
0 one
1 two
2 three
元组底层实现
元组和列表相似,本质也是一个数组,但是空间大小固定(不可变),不同于数组,python的元组做出了许多优化,来提升在程序中的效率
为了提高效率,避免频繁调用系统函数free和malloc向操作系统审批和释放空间,元组源文件中定义了一个free_list所有申请过的,小于一定大小的元组,在释放的时候会被放在这个free_list中,以供下次使用,也就是说如果以后在创建相同的元组,可以直接从缓存中载入
字典的底层实现
字典底层是维护一张哈希表,我们可以把它看成一个列表,列表的每个元素都存了哈希值、键、值三个元素,还需要一个indices表来辅助
indices = [None, None, index, None, index, None, index]
enteies = [
[hash0, key0, value0],
[hash1, key1, value1],
[hash2, key2, value2]
]
计算key的哈希值,在减去mask(字典的最小长度-1)得到一个数字,这个数字就是要插入的indices的下标位置
得到index值后会找indices的位置,但是此位置不是存的哈希值,而是存的len(enteies),表示该值在enteies的位置
如果出现了hash冲突则向下一个查找,直到不冲突为止
# 给字典添加一个值,key为hello,value为wordmy_dict['hello'] = 'word'# 假设是一个空列表,hash表初始如下indices = [None, None, None, None, None, None]
enteies = []
hash_value = hash('hello') # 假设值为 12343543index = hash_value & ( len(indices) - 1) # 假设index值计算后等于3,具体的hash算法本文不做介绍# 会找到indices的index为3的位置,并插入enteies的长度indices = [None, None, None, 0, None, None]# 此时enteies会插入第一个元素enteies = [
[12343543, 'hello', 'word']
]# 我们继续向字典中添加值my_dict['haimeimei'] = 'lihua'hash_value = hash('haimeimei') # 假设值为 34323545index = hash_value & ( len(indices) - 1) # 假设index值计算后同样等于 0# 会找到indices的index为0的位置,并插入enteies的长度indices = [1, None, None, 0, None, None]# 此时enteies会插入第一个元素enteies = [
[12343543, 'hello', 'word'],
[34323545, 'haimeimei', 'lihua']
]
列表与元组的区别
首先列表是可变的,元组是不可变的
元组的大小是固定的,列表是在会动态变化得
所有在效率上,元组是列表快得,
元组可以用来存储一个人得身高体重年龄等信息,或者记录某个坐标
列表可以存储多条数据,[“zhangsan”,”lisi”]
列表和字典的区别
list是有序的,dict在python3.6以后变为有序的
list通过索引取值,而dict通过键取值,dict取值更快
list随着数量的增加要查找的元素的话时间更旧,O(n),dict不会随着数量变化而变化,时间复杂度都是O(1)
dict占用的空间要比list大,可能大1.5倍
特征决定用途:
list一般可作为队列、堆栈使用,而dict一般作为聚合统计或者快速使用特征访问等
集合的底层实现
python的内置集合类型有两种:
set(): 一种可变的、无序的、有限的集合,其元素是唯一的、不可变的(可哈希的)对象。 frozenset(): 一种不可变的、可哈希的、无序的集合,其元素是唯一的,不可变的哈希对象。
set里的元素是不可变的,唯一的,但是set是可变的,所以set作为set的元素会报错
cpython中集合和字典非常相似,事实上,集合被实现带有控制的字典,只有键才是实际的集合元素。
可以快速的向集合中添加元素,删除元素,检查元素是否存在。平均时间复杂度为O(1),最坏为O(n)