第一部分 从奇怪的列表说起
1.1 错综复杂的复制
list_1 = [ 1 , [ 22 , 33 , 44 ] , ( 5 , 6 , 7 ) , { "name" : "Sarah" } ]
list_2 = list_1. copy( )
list_2[ 1 ] . append( 55 )
print ( "list_1: " , list_1)
print ( "list_2: " , list_2)
list_1: [1, [22, 33, 44, 55], (5, 6, 7), {'name': 'Sarah'}]
list_2: [1, [22, 33, 44, 55], (5, 6, 7), {'name': 'Sarah'}]
1.2 列表的底层实现
1.2.1 引用数组
列表存储的,实际上是元素的地址!!! 元素可以分散的存储在内存中,地址的存储在内存中是连续的。 如果直接把变量名赋值给另个一个变量,相当于没有变化,只不过又起了个名字。 当通过浅拷贝把list_1赋值给list_2时,相当于list_2拿到了一份与list_1独立的位置信息,但是指向的内容是完全一样的。 list_1[0]中存储的是一个数字的地址。 list_1[1]中存储的是一个地址列表的地址,地址列表中存储的是三个数字的地址。 list_1[2]中存储的是一个地址元组的地址,地址元组中存储的是三个数字的地址。 list_1[3]中存储的是一个字典的散列表的地址,字典的值就存在散列表中。
1.2.2 新增元素
list_1 = [ 1 , [ 22 , 33 , 44 ] , ( 5 , 6 , 7 ) , { "name" : "Sarah" } ]
list_2 = list ( list_1)
list_1. append( 100 )
list_2. append( "n" )
print ( "list_1: " , list_1)
print ( "list_2: " , list_2)
list_1: [1, [22, 33, 44], (5, 6, 7), {'name': 'Sarah'}, 100]
list_2: [1, [22, 33, 44], (5, 6, 7), {'name': 'Sarah'}, 'n']
新增元素时,相当于在两个列表中分别新存了一个地址,所以可以达到预期的效果。
1.2.3 修改元素
list_1 = [ 1 , [ 22 , 33 , 44 ] , ( 5 , 6 , 7 ) , { "name" : "Sarah" } ]
list_2 = list ( list_1)
list_1[ 0 ] = 10
list_2[ 0 ] = 20
print ( "list_1: " , list_1)
print ( "list_2: " , list_2)
list_1: [10, [22, 33, 44], (5, 6, 7), {'name': 'Sarah'}]
list_2: [20, [22, 33, 44], (5, 6, 7), {'name': 'Sarah'}]
当给list_1[0]重新赋值时,相当于把1的地址替换成了10的地址,list_2同理。
1.2.4 对列表型元素进行操作
list_1 = [ 1 , [ 22 , 33 , 44 ] , ( 5 , 6 , 7 ) , { "name" : "Sarah" } ]
list_2 = list ( list_1)
list_1[ 1 ] . remove( 44 )
list_2[ 1 ] += [ 55 , 66 ]
print ( "list_1: " , list_1)
print ( "list_2: " , list_2)
list_1: [1, [22, 33, 55, 66], (5, 6, 7), {'name': 'Sarah'}]
list_2: [1, [22, 33, 55, 66], (5, 6, 7), {'name': 'Sarah'}]
list_1[1]和list_2[1]都指向同一个地址列表,地址列表指向3个元素,对地址列表的操作,是改变了地址列表中的元素的指向,但是list_1[1]和list_2[1]的指向没有发生变化。
1.2.5 对元组型元素进行操作
list_1 = [ 1 , [ 22 , 33 , 44 ] , ( 5 , 6 , 7 ) , { "name" : "Sarah" } ]
list_2 = list ( list_1)
list_2[ 2 ] += ( 8 , 9 )
print ( "list_1: " , list_1)
print ( "list_2: " , list_2)
list_1: [1, [22, 33, 44], (5, 6, 7), {'name': 'Sarah'}]
list_2: [1, [22, 33, 44], (5, 6, 7, 8, 9), {'name': 'Sarah'}]
元组是不可变的,一旦定义好,就不能进行修改了。list_2[2]在执行加法操作后,产生了一个新的元组,元组中存储的是5个数字的地址,list_2[2]指向新的元组,list_1[2]仍然指向原来的元组。
1.2.6 对字典型元素进行操作
list_1 = [ 1 , [ 22 , 33 , 44 ] , ( 5 , 6 , 7 ) , { "name" : "Sarah" } ]
list_2 = list ( list_1)
list_1[ - 1 ] [ "age" ] = 18
print ( "list_1: " , list_1)
print ( "list_2: " , list_2)
list_1: [1, [22, 33, 44], (5, 6, 7), {'name': 'Sarah', 'age': 18}]
list_2: [1, [22, 33, 44], (5, 6, 7), {'name': 'Sarah', 'age': 18}
list_1[-1]中存储的是一个字典的散列表的地址,散列表中存储了一个值 ‘Sarah’。增加一个值,是在散列表中通过键找到相应的位置,把值存进去,散列表的地址没有变。
1.2.7 小结
有些数据类型,如列表和字典,是可变的,它可以地址不变而内容变化。 不可变的数据类型,如果内容变化,地址也会变化。
1.3 深拷贝——copy.deepcopy(列表)
浅拷贝之后 针对不可变元素(数字、字符串、元组)的操作,都各自生效了。 针对不可变元素(列表、字典、集合)的操作,发生了一些混淆。 引入深拷贝 深拷贝将所有层级的相关元素全部复制,完全分开,泾渭分明,避免了上述问题。
import copy
list_1 = [ 1 , [ 22 , 33 , 44 ] , ( 5 , 6 , 7 ) , { "name" : "Sarah" } ]
list_2 = copy. deepcopy( list_1)
list_1[ - 1 ] [ "age" ] = 18
list_2[ 1 ] . append( 55 )
print ( "list_1: " , list_1)
print ( "list_2: " , list_2)
list_1: [1, [22, 33, 44], (5, 6, 7), {'name': 'Sarah', 'age': 18}]
list_2: [1, [22, 33, 44, 55], (5, 6, 7), {'name': 'Sarah'}]
第二部分 神秘的字典
2.1 快速的查找
import time
ls_1 = list ( range ( 1000000 ) )
ls_2 = list ( range ( 500 ) ) + [ - 10 ] * 500
start = time. time( )
count = 0
for n in ls_2:
if n in ls_1:
count += 1
end = time. time( )
print ( "查找{}个元素,在ls_1列表中的有{}个,共用时{}秒" . format ( len ( ls_2) , count, round ( ( end- start) , 2 ) ) )
查找1000个元素,在ls_1列表中的有500个,共用时3.92秒
import time
d = { i: i for i in range ( 100000 ) }
ls_2 = list ( range ( 500 ) ) + [ - 10 ] * 500
start = time. time( )
count = 0
for n in ls_2:
try :
d[ n]
except :
pass
else :
count += 1
end = time. time( )
print ( "查找{}个元素,在ls_1列表中的有{}个,共用时{}秒" . format ( len ( ls_2) , count, round ( end- start) ) )
查找1000个元素,在ls_1列表中的有500个,共用时0秒
分别用列表和字典实现1000个元素的查找,字典比列表快得多,why?
2.2 字典的底层实现
2.2.1 字典的创建过程
第一步:创建一个散列表(稀疏数组 长度 >> 元素个数)
d = { }
print ( hash ( "python" ) )
print ( hash ( 1024 ) )
print ( hash ( ( 1 , 2 ) ) )
1989505856890947430
1024
3713081631934410656
d[ "age" ] = 18
print ( hash ( "age" ) )
-4940077774348628352
第二步:根据计算的散列值确定其在散列表中的位置 极个别时候,散列值会发生冲突,则内部有相应的解决冲突的办法 第三步:在该位置上存入值
2.2.2 键值对的访问过程
d[ "age" ]
第一步:计算要访问的键的散列值 第二步:根据计算的散列值,通过一定的规则,确定其在散列表中的位置 第三步:读取该位置上存储的值 如果存在,则返回该值 如果不存在,则报错KeyError
2.3 小结
字典数据类型,通过空间换时间,实现了快速的数据查找 也就注定了字典的空间利用效率低下 因为散列值对应位置的顺序与键在字典中显示的顺序可能不同,因此表现出来字典是无序的 为什么要用稀疏数组? 如果长度刚好,会产生很多位置冲突,增加了处理效率 列表是从头开始查找的,字典只需要计算散列值就可以确定位置,所以字典实现了比列表更快速的查找
第三部分 紧凑的字符串
通过紧凑数组实现字符串的存储 数据在内存中是连续存放的,效率更高,节省空间 同为序列类型,为什么列表采用引用数组,而字符串采用紧凑数组? 字符串中每个元素是单一的字符,大小可控,方便预留空间;列表元素多种多样,没有办法预留空间,不如把数据存储在其它位置,列表中只存储地址。
第四部分 是否可变
4.1 不可变类型:数字、字符串、元组
在生命周期中保持内容不变 换句话说,改变了就不是它自己了(id变了) 不可变对象的 += 操作 实际上创建了一个新的对象
x = 1
y = "Python"
print ( "x id:" , id ( x) )
print ( "y id:" , id ( y) )
x += 2
y += "3.7"
print ( "x id:" , id ( x) )
print ( "y id:" , id ( y) )
x id: 140718440616768
y id: 2040939892664
x id: 140718440616832
y id: 2040992707056
元组并不是总是不可变的 元组中的元素是不可变类型的时候,元组才是不可变的,如果元组中有可变元素,如列表,那么元组还是可变的。
t = ( 1 , [ 2 ] )
t[ 1 ] . append( 3 )
print ( t)
(1, [2, 3])
4.2 可变类型:列表、字典、集合
id 保持不变,但是里面的内容可以变 可变对象的 += 操作,实际在原对象的基础上就地修改
ls = [ 1 , 2 , 3 ]
d = { "Name" : "Sarah" , "Age" : 18 }
print ( "ls id:" , id ( ls) )
print ( "d id:" , id ( d) )
ls += [ 4 , 5 ]
d_2 = { "Sex" : "female" }
d. update( d_2)
print ( "ls id:" , id ( ls) )
print ( "d id:" , id ( d) )
ls id: 2040991750856
d id: 2040992761608
ls id: 2040991750856
d id: 2040992761608
第五部分 列表操作的几个小坑
5.1 删除列表内的特定元素
存在运算删除法 缺点:每次存在运算,都要从头对列表进行遍历、查找、效率低
alist = [ "d" , "d" , "d" , "2" , "2" , "d" , "d" , "4" ]
s = "d"
while True :
if s in alist:
alist. remove( s)
else :
break
print ( alist)
['2', '2', '4']
alist = [ "d" , "d" , "d" , "2" , "2" , "d" , "d" , "4" ]
for s in alist:
if s == "d" :
alist. remove( s)
print ( alist)
['2', '2', 'd', 'd', '4']
for循环会记忆索引,删除第一个 “d” 后,列表变成 [“d”, “d”, “2”, “2”, “d” ,“d”, “4”],for循环记忆的是,取过第0个元素了,接下来要取第一个元素,也就是新列表的第0个元素被略过了,以此类推。
负向索引
alist = [ "d" , "d" , "d" , "2" , "2" , "d" , "d" , "4" ]
for i in range ( - len ( alist) , 0 ) :
if alist[ i] == "d" :
alist. remove( alist[ i] )
print ( alist)
['2', '2', '4']
5.2 多维列表的创建
ls = [ [ 0 ] * 10 ] * 5
print ( ls)
ls[ 0 ] [ 0 ] = 1
print ( ls)
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
[[1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
[0]*10 本身是一个列表,对其进行复制,指向的是同一个列表。
解析语法
ls = [ [ 0 ] * 10 for i in range ( 5 ) ]
print ( ls)
ls[ 0 ] [ 0 ] = 1
print ( ls)
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
[[1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]