python数据类型
文章目录
类型 | 概念 |
---|---|
容器序列 | 列表、元组、集合(存放的是他们所包含的任意类型的对象的引用) |
扁平序列 | str,bytes,bytearray,memoryview,array.array(存放的是值,是一段连续的存储空间,空间更加紧凑,里面只能存放字符串、字节和数值这种基本类型) |
可变序列 | list,bytearray,array.array,collections.deque,memoryview |
不可变序列 | tuple,str,bytes |
1. 数据类型
类型 | 概念 |
---|---|
元组 | 元组是不可变的,一但创建就不可进行修改,用小括号()来表示,元素用逗号隔开。支持索引和切片操作,可以使用下标访问元素,也支持一些常用的操作,比如获取元素个数、查找元素等。由于元组不可变,元组的访问速度比列表更快。创建元组是需要一次性分配内存空间,所以当元组中元素个数较多时,对内存的占用较大 |
列表 | 列表是有序的可变序列,可以动态添加、删除和修改元素,由于列表使用连续的内存块来存储元素,所以访问速度比元组慢,但是在创建列表时只需要分配所需的内存空间,所以对内存的占用较小 |
字典 | 字典是无序的键值对集合,可以通过键来快速访问值。由于字典使用哈希表实现,所以访问速度很快,但是在创建字典时需要分配额外的内存空间来存储哈希表,所以对内存的占用较大 |
集合 | 是一种无序且不重复的数据类型,可以用于去重和判重。集合使用哈希表表示,所以访问速度快。集合中的元素必须是可哈希的,因此集合中不能包含可变类型的元素,如列表、字典等,提供两种删除方法remove和discard,效率一样,前者删除元素不存在报错,后者不报错。集合支持一些基本的运算,如并集、交集、差集等 |
类型 | 概念 |
---|---|
可变 | 定义好了之后还可以对其进行修改 |
不可变 | 定义好了之后不能对其修改 |
元组、列表、集合之间可以使用set、list、tuple相互转化
a = [1,2,3]
b = set(a) # 转集合
c = tuple(a) # 转元组
d = list(a) # 转列表
1.1 元组
元组创建后不能做修改,创建元组时一次性分配内存,支持索引切片操作,可以使用下标访问元素,元组的访问速度比列表更快
# 元组 不能修改值
a = (1, 2, 3, 4, 5, 6) # 如果元组只有一个元素,需要加逗号 如:a = (1,)
print(a.count(4)) # 统计元素个数
# 1
print(a.index(4)) # 查找元素4所在位置索引,不存在则报错
# 3
print(a.index(4, 2, 5)) # 从索引2到5之间查找元素4所在索引
# 3
嵌套遍历
# 嵌套遍历
b = (1, 2, (3, 4), (5, 6))
for v in b:
if isinstance(v, tuple): # 判断参数一是否是参数二的类型对象
for v2 in v:
print(v2)
else:
print(v)
组包,拆包
# 组包
a = 1, 2, 3, 4, 5
print(a)
# (1, 2, 3, 4, 5)
# 拆包 元素数量必须一致,除非使用*来表示忽略多余的元素
b, c, d, e, f = a
print(b, c, d, e, f)
#1 2 3 4 5
具名元组
:collections.namedtuple是工厂函数,可以用来构建一个带字段名的元组和一个有名字的类(带名字的类对调试有很大帮助)。用namedtuple构建的类的实例所消耗的内存根元组是一样的。
用具名元组记录一个城市信息代码如下
from collections import namedtuple
# 创建具名元组需两个参数,一个类名,一个是类的各字段的名字,后者可有数个字符串组成的组成的可迭代对象或者由空格分隔开的字段名组成的字符串
City = namedtuple('City', 'name country coordinates')
# 以一串参数的形式传入到构造函数中
tokyo = City('Tokyo', 'JP', (32.1, 23.3))
# 可通过字段名或位置来获取一个字段的信息
print(tokyo)
# City(name='Tokyo', country='JP', coordinates=(32.1, 23.3))
print(tokyo.name)
# Tokyo
print(tokyo.coordinates)
# (32.1, 23.3)
print(tokyo[1])
# JP
具名元组还有一些很有用的属性:_fileds类属性,类方法_make(iterable)和实例方法_asdict()
from collections import namedtuple
# 创建具名元组需两个参数,一个类名,一个是类的各字段的名字,后者可有数个字符串组成的组成的可迭代对象或者由空格分隔开的字段名组成的字符串
City = namedtuple('City', 'name country coordinates')
# 类的所有字段名称元组
print(City._fields)
# ('name', 'country', 'coordinates')
# 以一串参数的形式传入到构造函数中
tokyo = City('Tokyo', 'JP', (32.1, 23.3))
# _make接受可迭代对象来生成一个实例
print(City._make(tokyo))
# City(name='Tokyo', country='JP', coordinates=(32.1, 23.3))
# _asdict把具名元组以字典的新式返回
print(City._make(tokyo)._asdict())
# {'name': 'Tokyo', 'country': 'JP', 'coordinates': (32.1, 23.3)}
列表和元组的方法和属性
列表 | 元组 | 数组 | 双向队列 | ||
---|---|---|---|---|---|
s.__add__(s2) | √ | √ | √ | s1+s2,拼接 | |
s.__iadd__(s2) | √ | √ | √ | s += s2,就地拼接 | |
s.append(e) | √ | √ | √ | 尾部添加一个元素 | |
s.byteswap | √ | 翻转数组内每个元素的字节序列,转换字节升序 | |||
s.clear() | √ | √ | 删除所有元素 | ||
s.__contains__(e) | √ | √ | √ | s是否包含e | |
s.copy() | √ | 浅拷贝 | |||
s.__copy__() | √ | √ | 对copy.copy的支持 | ||
s.__deepcopy__() | √ | 对copy.deepcopy的支持 | |||
s.count(e) | √ | √ | √ | √ | e在s中出现的次数 |
s.__delitem__(p) | √ | √ | √ | 把位于P的元素删除 | |
s.extend(it) | √ | √ | √ | 把可迭代的对象it追加到s | |
s.frombytes(b) | √ | 将压缩成机器值的字节序列读出来添加到尾部 | |||
s.fromfile(f, n) | √ | 将二进制文件f内含有机器值读取出来添加到尾部,最多添加n项 | |||
s.fromlist(l) | √ | 将列表里的元素添加到尾部,如果其中国内任何一个元素导致了TypeError异常,那么所有的添加都会取消 | |||
s.__getitem__(p) | √ | √ | √ | √ | s[p],获取位子p的元素 |
s.__getnewargs__() | √ | 在pickle中支持更加优化的序列化 | |||
s.index(e) | √ | √ | √ | 在s中找到元素e第一次出现的位置 | |
s.insert(p, e) | √ | √ | 在位置p之前插入元素e | ||
s.itemsize | √ | 数组中每个元素的长度是几个字节 | |||
s.__iter__() | √ | √ | √ | √ | 获取s的迭代器 |
s.__len__() | √ | √ | √ | √ | len(s),元素的数量 |
s.__mul__() | √ | √ | √ | s*n,n个s的重复拼接 | |
s.__imul__() | √ | √ | s *= n,就地重复拼接 | ||
s.__rmul__() | √ | √ | √ | n*s,反向拼接 | |
s.pop([p]) | √ | √ | √ | 删除最后元素,或者删除位于p的元素(填了p参数),并返回值 | |
s.remove(e) | √ | √ | √ | 删除s中的第一次出现的e | |
s.reverse() | √ | √ | √ | 倒序排列 | |
s.__reversed__() | √ | √ | 返回s的倒序迭代器 | ||
s.__setitem__(p, e) | √ | √ | √ | s[p] = e,把元素e放在位置p,替代原来该位置的元素 | |
s.sort([key], [reverse]) | √ | 对s进行排序,可选键和是否倒序 | |||
s.tobutes() | √ | 所有元素的机器值以bytes对象的形式放回 | |||
s.tofile(f) | √ | 所有元素以机器值的新式写入文件 | |||
s.tolist() | √ | 把数组转化为列表,列表里的元素类型是数字对象 | |||
s.typecode | √ | 返回只有一个字符的字符串,代表数组元素在C语言中的类型 |
1.2 列表
列表是有序的可变序列,可以动态添加、删除和修改元素,使用连续的内存块来存储元素,访问速度比元组慢,创建列表时只需要分配所需的内存空间
列表操作函数 | 含义 |
---|---|
a.pop(0) | 索引删除 |
a.insert(0, 6) | 按索引插入 |
a.append(7) | 末尾插入 |
a.count(5) | 5出现的次数 |
a.remove(1) | 删除1 |
a.reverse() | 反转 |
a.sort() | 排序(就地排序,未产生新对象) |
sorted(a) | 排序(产生新对象) |
1 in a | 判断a中是否有1 |
tuple(a) | 转元组 |
list(a) | 转列表 |
shuttle(a) | 对a列表顺序打乱 |
all(it) | it中所有元素都为真时返回True,否则False,all([])返回True |
any(it) | 只要it中有元素都为真时返回True,否则False,all([])返回False |
max(it, [key-,][default=]) | 返回it中最大的元素,*key为排序函数,如果可迭代对象为空,返回default |
min(it, [key-,][default=]) | 返回it中最小的元素,*key为排序函数,如果可迭代对象为空,返回default |
reduce(func, it, [initial]) | 把前两个元素传给func,然后把计算结果和第三个元素传给func,以此类推,返回最后结果,如果提供了initial,将其作为第一个传入 |
sum(it, start=0) | it中所有元素综合,如果提供可选的start,会将其加上 |
# 生成器表达式模式创建列表
print([i for i in range(5)])
# [0, 1, 2, 3, 4]
print([i for i in range(5) if i % 2 == 0])
# [0, 2, 4]
print([(i, j) for i in range(2) for j in range(3)])
# [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)]
# 字符串转列表
print(list('hahaha')) # ['h', 'a', 'h', 'a', 'h', 'a']
a=[[8,2,3,4],[4,3,2,4],[2,4,1,3], [8,5,6,2]]
b = sorted(a,key = lambda index_value:index_value[3]) # 以每个元素的第四个元素排序
# 列表排序
b = [{'id':4, 'num':43}, {'id':2, 'num':32}, {'id':6, 'num':86}]
# 按id升序排序
b.sort(key=lambda d: d['id'])
print(b) # [{'id': 2, 'num': 32}, {'id': 4, 'num': 43}, {'id': 6, 'num': 86}]
# 按num降序排序
b.sort(key=lambda d: d['num'], reverse=True)
print(b) # [{'id': 6, 'num': 86}, {'id': 4, 'num': 43}, {'id': 2, 'num': 32}]
列表注意事项:在使用列表赋值的时候要注意深拷贝与浅拷贝,使用不熟练很容易出问题
import copy
a = [1, 2, [3, 4]]
b = a # a/b中其中一个变化影响另一个
c = a[:] # a里面的元素变化,c中的内容不发生变化,a里面的列表发生变化,c里面也随之变化
d = copy.deepcopy(a) # d不随a变化而变化
e = copy.copy(a) # 等同于c=a[:]
a[0] = 0 # b被改变
a[2][0] = 0 # c、e被改变
print(a) # a与b指向同一个地址
print(b)
print(c)
print(d) # 不变化
print(e)
print(id(a), id(b), id(c), id(d), id(e)) # a与b指向同一个地址
print(id(a[2]), id(c[2]), id(e[2])) # a[2]、c[2]与e[2]指向同一个地址
'''
[0, 2, [0, 4]]
[0, 2, [0, 4]]
[1, 2, [0, 4]]
[1, 2, [3, 4]]
[1, 2, [0, 4]]
2791583781248 2791583781248 2791586036672 2791586848384 2791586129088
2791583783104 2791583783104 2791583783104
'''
列表的+和*的序列操作
:使用+和*操作时,+两侧的序列有相同类型的数据构成,操作时并不会修改+两侧的数据,Python会新建一个拥有类型的序列来保存结果,如果要对一个序列拼接几次,可以使用*,代码如下
a = [1, 2, 3]
print(a*3)
# [1, 2, 3, 1, 2, 3, 1, 2, 3]
print('abc'*3)
# abcabcabc
list.sort
:该方法就地排序列表,不会把原列表赋值一份,这就是函数返回值为None的原因,本函数不会新建一个列表。也就是说如果某函数实现对对象进行就地修改,则该函数返回None,则意味着传入的参数发生了变化,并未产生新对象。如random、shuffle函数
内置函数sorted
:内置函数sorted会新建一个列表最为返回值,可接收任何可迭代参数(包括不可变序列或生成器),最后都会返回一个列表
两者都有两个可选参数,reverse表示升序或降序,默认升序(false),key表示排序算法依赖对比的关键字,如key=str.lower排序忽略大小写,key=len基于字符长度排序,默认用元素自己的值排序,key还可在内置函数min()和max()中起作用。有些标准库里的函数也接受该函数
bisect管理已排序序列
:bisect包含bisect和insort函数,两个函数都是利用二分查找法在有序序列中查找或插入元素
insort(seq, item)把变量item插入到有序的seq中,并保持seq的升序,bisect模块文档
1.3 字典
字典是无序的键值对集合,可以通过键来快速访问值。使用哈希表实现,访问速度很快,创建字典时需要分配额外的内存空间来存储哈希表,内存的占用较大
# 通过建获取值
a = {'a':'qg','ba':'ygr','yu':'ky'}
print(a['a']) # 键值不存在时报错
print(a.get('a')) # 键值不存在时输出None
遍历
a = {'a':'qg','ba':'ygr','yu':'ky'}
for i in a:
print(i)
for key in a.keys(): # 获取键
print(key, a[key])
for value in a.values(): # 获取值
print(value)
for key, value in a.items(): # 获取键值对
print(key, value)
增删改查
# 增
a['a'] = 1
# 改
a['a'] = 2
# 查
print(a['a'])
print(a.get('a'))
# 删
a.popitem() # 删除最后一个
a.pop('b') # 指定删除
del a['a'] # 删除指定
a.clear() # 清空
字典 | |
---|---|
d.keys() | 获取所有键 |
d.values() | 返回字典所有值 |
d.items() | 所有键值对 |
d.clear() | 移除所有元素 |
d.pop(k, [default]) | 删除并返回d[k],没有则返回None(或default指定的参数) |
d.popitem() | 随机返回一个(或最后一个)键值对并删除 |
d.get(k, [default]) | 返会k对应的值,如果没有则返回None(或default指定的参数) |
d.setdefault(k, [default]) | 若字典有k键值则返回对应值,没有则d[k]=default并返回default |
d.__contains__(k) | k是否在d中 |
d.copy() | 浅拷贝 |
d.__delitem__(k) | del d[k],删除键值为k的元素 |
d.fromkeys(it, [initial]) | 将迭代器it里的元素设置为映射里的键,如果有initial参数就把它作为这些键对应的值 |
d.__getitem__(k) | 让字典能用d[k]获取对应值 |
d.__iter__() | 获取键的迭代器 |
d.__len__() | 能使用len(d) |
d.__setitem__(k, v) | 实现d[k] = v |
d.update(m, [**kargs]) | 更新d中对应键值对 |
用setdefault处理找不到的键
:当字典d[k]找不到键时,会抛出一个异常,可以用d.get(k, [default])来代替,找不到是返回None或默认值,d.get()不是处理找不到键的最好方法,该方法效率低
创建查询效率
字典查找插入新值效率对比(该键值不一定在字典中)
# 方法一
if key not in my_dict:
my_dict[key] = []
my_dict[key].append(new_value)
# 方法二
content = my_dict.get(key, [])
content.append(value)
my_dict[key] = content
# 方法三 效率最高
my_dict.setdefault(key, []).append(value)
上面三种方法,第三种效率最
高,只有遍历一次字典即可,前面两种都要遍历两次或三次
import collections
# 这里指定没找到键值是给个空列表(没指定会报错,如指定为list)
index = collections.defaultdict(list)
# 如果index中没有key记录,则default_factory会被调用,为查询不到的键创建一个值,这个值在这里是空列表,空列表返回给index[key]
index[key].append(value)
collections.Counter可用来计算单词中共各个字母出现的次数
import collections
a = collections.Counter('afdsgdfafasdafs')
print(a)
# Counter({'a': 4, 'f': 4, 'd': 3, 's': 3, 'g': 1})
a.update('afdwerfefasd')
print(a)
# Counter({'f': 7, 'a': 6, 'd': 5, 's': 4, 'e': 2, 'g': 1, 'w': 1, 'r': 1})
print(a.most_common(2))
# [('f', 7), ('a', 6)]
1.4 集合(自动去重)
无序且不重复的数据类型,可以用于去重和判重。使用哈希表表示,访问速度快。集合中的元素必须是可哈希的,集合中不能包含可变类型的元素,如列表、字典等,提供两种删除方法remove和discard,效率一样,前者删除元素不存在报错,后者不报错。集合支持一些基本的运算,如并集、交集、差集等,集合不支持下标索引
方法 | 描述 |
---|---|
s.add(e) | 元素e加入s中 |
s.pop() | 移除一个元素并返回值,如果s为空则抛出异常 |
s.remove(e) | s中移除e,如果不存在则抛出异常 |
s.clear() | s清空 |
s.copy() | 浅拷贝 |
s.discard(e) | 如果s中存在e则将其移除 |
s.__iter__() | 返回s迭代器 |
s.__len__() | len(s) |
# 集合 不会有重复数据,自动去重
a = {1, 2, 3, 4, 4, 3, 1}
print(a)
# {1, 2, 3, 4}
for i in a:
print(i)
# 集合不支持下标索引
# print(a[0]) # 报错
直接定义{1, 2, 3}比set([1, 2, 3])定义更快
集合的数学运算符,有些方法会生成新的集合,有些就地修改集合
python运算符 | 方法 | 描述 |
---|---|---|
s&z | s.__and__(z) | s和z的交集 |
s.__rand__(z) | &的反向操作 | |
s.intersection(it[, …]) | s与参数的交集(参数可为list,自动转为set类型) | |
s &= z | s.__iand__ | 把s跟新为s和z的交集 |
s.intersection_update(it[, …]) | s与参数的交集并赋值给s(参数可为list) | |
s|z | s.__or__(z) | s和z的并集 |
s.__ror__(z) | |的反向操作 | |
s.union(z[, …]) | s与参数的并集(参数可为list) | |
s-z | s.__sub__(z) | s和z的差集 |
s.__rsub__(z) | -的反向操作 | |
s.difference(it[, …]) | s与参数的差集(参数可为list) | |
s -= z | s.__isub__(z) | s更新为s与z的差集 |
s.different_update(it[, …]) | s与参数的差集并赋值给s(参数可为list) | |
s.symmetric_differenct(it) | s与参数的对称差集(参数可为list) | |
s^z | s.__xor__(z) | s与z的对称差集 |
s.__rxor__(z) | ^的反向操作 | |
s.symmetric_difference_update(it[, …]) | s与参数的对称差集并赋值给s(参数可为list) | |
s ^= z | s.__ixor__(z) | s更新为s与z的对称差集 |
集合的比较运算符,返回布尔值
运算符 | 方法 | 描述 |
---|---|---|
s.isdisjoint(z) | 查看s和z是否不相交(没有共同元素) | |
e in s | s.__contains__(e) | 元素e是否属于s |
s <= z | s.__le__(z) | s是否为z的子集 |
s.issubset(it) | 查看s是否为参数的子集(参数可为list,自动转为set类型) | |
s < z | s.__lt__(z) | z是否为z的真子集 |
s >= z | s.__ge__(e) | s是否为z的父集 |
s.issuperset(it) | s是否为参数的父集(参数可为list) | |
s > z | s.__gt__(z) | s是否为z的真父集 |
1.5 数组
如果只保存数字,array.array效率比list更高效,数组(array)支持.pop/.insert/.extend操作,支持从文件读取和存入文件(.frombytes和.tofile)。创建数组需要一个类型码表示存放怎样的类型,如array(‘b’)中的b表示只存放一个字节大小的整数(-128到127),序列很长时刻节省大量空间
下列实例创建浮点数数组并放到文件里,再读取出来
from array import array
from random import random
# 创建数组 写入文件
floats = array('d', (random() for i in range(1000)))
print(floats[-1])
fp = open('a.bin', 'wb')
floats.tofile(fp)
fp.close()
# 读取文件
floats2 = array('d')
fp = open('a.bin', 'rb')
floats2.fromfile(fp, 1000)
fp.close()
print(floats2[-1])
用array.fromfile从二进制文件里读取浮点数比从文本文件里读取的速度快60倍
,利用array.tofile写入到二进制文件比以每一行一个浮点数的方式把所有数字写入文本文件要快7倍。浮点数存储在二进制文件比存储在文本文件更省空间
Python3.4开始,数组不支持list.sort就地排序方法,排序得用sorted新建一个数组
a = array.array(a.typecode, sorted(a))
要在不打乱顺序的情况下加入元素,得用bisect.insort方法
import bisect
a = [1, 6, 8, 12, 54]
b = [2, 3, 9]
for c in b:
# 获取插入位置
# position = bisect.bisect(a, c)
# print(position)
bisect.insort(a, c)
print(a)
数组内网视图
:memoryview是一个内置类,可以在不复制内容的前提下操作同一个数组的不同切片,在不复制内容的前提下在数据结构之间共享内存,其中数据结构可以是任何形式,如PIL图片,SQLite数据库和Numpy数组等。该功能在处理大型数据的时候非常重要。memoryview.cast能用不同方式读写同一块内存数据,而且内容字节不会随意移动,memoryview.cast会把同一块内存里的内容打包成一个全新的memoryview对象给你
import array
a = array.array('h', [-2, -1, 0, 1, 2])
memv = memoryview(a)
print(len(memv))
# 5
print(memv[0])
# -2
memv_oct = memv.cast('B')
print(memv_oct.tolist())
# [254, 255, 255, 255, 0, 0, 1, 0, 2, 0]
memv_oct[5] = 4
print(a)
# array('h', [-2, -1, 1024, 1, 2])
1.6 队列
在添加或删除列表的第一个元素的时候很耗时间,该操作会牵扯到移动列表里的所有元素。collections.deque类(双向队列)是可以快速从两端添加或删除元素的数据类型。如果想要存放最近使用的几个元素,可以使用deque,因为创建deque的时候可以指定队列大小,如果满了可以从反向端删除过期的元素,在尾部添加新的元素。队列只对头尾操作进行了优化,从队列中间删除元素操作会慢些
from collections import deque
a = deque(range(10), maxlen=10)
print(a)
# deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
a.rotate(3)
print(a)
# deque([7, 8, 9, 0, 1, 2, 3, 4, 5, 6], maxlen=10)
a.rotate(-4)
print(a)
# deque([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], maxlen=10)
a.appendleft(-1)
print(a)
# deque([-1, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
a.extend([11, 12])
print(a)
# deque([2, 3, 4, 5, 6, 7, 8, 9, 11, 12], maxlen=10)
可用随机生成浮点数做实验,测试集合、列表、字典的运算速度
2. 元组、列表、字典、集合性能
查询元素速度:字典或集合优于元组优于列表(in判断)
代码中,包含操作频率很高时,用set集合会更合适(如判断某元素是否出现的in),集合不是序列,set是无序的
3. 深拷贝与浅拷贝
对于可变对象
来说,浅拷贝只拷贝第一层数据,深拷贝会逐层进行拷贝;对于不变对象
来说,无论深浅拷贝,都不会进行拷贝,只是引用的赋值
浅拷贝使用copy.copy()
深拷贝使用copy.deepcopy()
# 当对不可变对象对象进行赋值时,实际并不会改变原值空间的内容,会开辟一个新空间来保存新数据并指向
# 引用的赋值
a = 1
b = a
print(a, b) # 1 1
# 地址都是一样的
print(id(a), id(b), id(1)) # 140704504275752 140704504275752 140704504275752
a = 2
print(a, b) # 2 1
# 地址都是一样的
print(id(2), id(a), id(b), id(1)) # 140704504275784 140704504275784 140704504275752 140704504275752
不可变对象:无论深浅拷贝,都属于是引用的赋值
import copy
a = 1
b = copy.copy(a) # 浅拷贝
print(id(1), id(a), id(b)) # 140704504275752 140704504275752 140704504275752
c = copy.deepcopy(a)
print(id(1), id(a), id(c)) # 140704504275752 140704504275752 140704504275752
可变对象的拷贝
浅拷贝:值不变,地址相同
深拷贝:值不变,地址不同
import copy
# 引用赋值 a/b中其中一个变化影响另一个
a = [1, "a", [3, 4]]
b = a
a[0] = 0
a[2][0] = 0
print(a) # [0, 'a', [0, 4]]
print(b) # [0, 'a', [0, 4]]
print(id(a), id(b)) # 2743766880640 2743766880640
# 浅拷贝
a = [1, "a", [3, 4]]
b = copy.copy(a) # 等同于b=a[:]切片赋值
a[0] = 0
a[2][0] = 0
# 如果拷贝成功: 第一层值相同、地址不同、内部值与地址相同
print(a) # [0, 'a', [0, 4]]
print(b) # [1, 'a', [0, 4]]
print(id(a), id(b)) # 1845964947648 1845964235968
print(id(a[2]), id(b[2])) # 1845964955264 1845964955264
# 深拷贝
a = [1, "a", [3, 4]]
b = copy.deepcopy(a) # 等同于b=a[:]切片赋值
a[0] = 0
a[2][0] = 0
# 如果拷贝成功: 1.值相同,2.地址不同
print(a) # [0, 'a', [0, 4]]
print(b) # [1, 'a', [3, 4]]
print(id(a), id(b)) # 1994022066368 1994025713856
print(id(a[2]), id(b[2])) # 1994022064512 1994025721024
4. 就地修改与新建赋值(=和+=)
a+=b背后的特殊方法时__iadd__,如果一个类没有这个方法则会调用__add__方法。前者就地加法,不会创建被关联新对象,后者等同于a=a+b,先创建一个对象保存运算结果,然后再赋值给a。可变序列一般都实现了__iadd__方法,不可变序列不支持这个操作。以*=为例,可变与不可变区别代码如下
# 运行前后id没变,在原序列是修改,即就地修改
a = [1, 2, 3]
print(id(a)) # 2668697507776
a *= 2
print(id(a)) # 2668697507776
print(a) # [1, 2, 3, 1, 2, 3]
# 运行前后id发送变化,即创建了新序列
b = (1, 2, 3)
print(id(b)) # 2668704649408
b *= 2
print(id(b)) # 2668701264736
print(b) # (1, 2, 3, 1, 2, 3)
对于不可变序列进行重复拼接效率会更低,每次都有个新对象,解释器需把原来的复制到新对象,再加新元素(str是例外,程序会为其流出额外的可扩展空间,运算不涉及赋值原有字符串到新位置)
5. 散列表(哈希表)
散列表:是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表
字典使用散列表实现的,散列表是一个稀疏数组(存在空白元素的数组),字典的每个键值对占用一个表元,每个表元有两部分,分别是对键值的引用和对值的引用,所有表元大小一致,可以通过偏移量来读取表元,Python会设法保证大概1/3的表元是空的,在快要达到阈值的时候,原有的散列表会被复制到一个更大的空间。把对象放入散列表会先计算元素键的散列值,python中用hash()来做这件事
字典查找a[key]:先计算hash(key)的散列值,该值的最低几位作为偏移量在散列表里面查找表元(具体几位看当前散列表大小),查找到为空则抛出异常,否则会有一对键值对,接着检验该键值与所搜寻的是否一致,一致就返回,不一致就成为散列冲突,碰到这种情况会使用散列值的另一部分用特殊的方式处理再当做索引来寻找表元
字典的键必须是可散列的,字典在内存的粗才能开销很大(散列表形式),键查询很快(空间换时间),键的次序取决于添加顺序,往字典中添加新建肯回改变已有键的顺序(扩容)
set实现也依赖散列表,集合的元素必须是可散列的,集合很消耗内存,可以很高效的判断元素是否存在于某个集合中,元素的次序取决于被添加到集合里的次序,往集合里添加元素可能会改变集合里已有元素的次序
python先运行右边,比如a = b+c,先计算b+c,在赋值给a,如果右边出错,也就没有a
原文地址:https://blog.csdn.net/weixin_46287157/article/details/141903428
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!