Python 学习笔记(02) 数据类型及数据结构
文章目录
2.1 类型定义
类型的存在是由于在程序设计语言中不允许存在语法歧义,需要明确数据的含义。
类型即为对【数据】的一种划分。
可以将类型定义划分为标准类型、数值类型以及其他类型。
2.2 标准类型
2.2.1 标准数据类型
1)Number(数字)
int(整型)、float(浮点型)、bool(布尔型,True/ False)、complex(复数)
2)String(字符串)
3)List(列表)
4)Tuple(元组)
5)Sets(集合)
6)Dictionary(字典)
其中字符串、列表、元组、集合以及字典并称为容器类型。
2.2.2 标准类型操作符
1)算数运算符
+ - * / # 加减乘除
% # 取模(除法余数)
** # 求幂
// # 取整除,商的整数部分
2)比较运算符
== != # 相等 不等
> >= < <= # 大于 大于等于 小于 小于等于
3)赋值运算符
= # 直接赋值
+=、-=、*=、/=、%=、**=、//= # 加后赋值 减后赋值 乘后赋值 除后赋值 取余后赋值 求幂后赋值 取整后赋值
4)位运算符
# 与二进制操作相关
& | ^ - # 与 或 异或 取反
<< >> # 左移 右移
"""
& 与0取0,全1为1
| 或1为1,全0为0
^ 相异为1,相同为0
- 由0变1,由1变0
<< 高位丢弃,低位补0,等于*2
>> 有符补符,无符补0,低位丢弃,等于/2
"""
5)逻辑运算符
and or not # 与 或 非
"""
and 只有前后条件都为真时方可执行
or 前后条件有一个为真时即可执行
not 条件不成立时方可执行
"""
6)成员运算符
in 、 not in
7)身份运算符
is 、 is not # 是否引用自同一个对象,相当于id(x) == id(y)
"""
is 和 == 的区别:
is 用于检查两个变量的引用对象是否为同一个;
== 用于检查引用变量的值是否相等
"""
2.2.3 相关内建函数(BIF)
type() # 查询数据类型
isinstance() # 查询是否为...的实例
issubclass() # 查询是否为...的子类
"""
注意区分类型和继承,子类继承于父类,但子类不是一种父类类型
"""
str() repr() # 返回对象的【字符串表示】
dir() # 查询一个【类或对象】的所有属性或方法
2.3 数值类型
数值类型可以被分为整数类型、浮点数类型、复数类型。
1)整数类型
十六进制 0x
二进制 0b
八进制 0o
2)浮点数类型
3)复数类型 z = a + bj
a为实数部分(z.real),b为虚数部分(z.imag),且a、b都属于浮点数类型。
2.3.1 数值类型之间关系
在运算时,整数与浮点数运算结果为浮点数类型,整数、浮点数与复数运算结果为复数类型。
整数 -> 浮点数 -> 复数
在混合运算时,结果为【最宽类型】。
同时各数值类型之间可以通过 **int()/ float()/ complex()**函数进行互相转换。
2.3.2 相关函数
import math
abs() # 取绝对值
math.ceil() math.floor() # 上入函数 下舍函数
divmod(a, b) # 结果为浮点数元组(a/b, a%b)
pow(a, b) # 求a的b次幂
round() # 四舍五入
2.3.3 随机数函数
import random
random.randint(a, b) # 随机生成一个整型数,其值范围为[a, b]
random.randrange(a, b, step) # 指定范围内取随机数,且(b - a)% step = 0
random.uniform(a, b) # 随机浮点型,其值范围为[a, b]
random.random() # 随机浮点型,其值范围为[0, 1)
random.choice() # 随机序列中的一个元素
random.shuffle(lst) # 将序列所有元素随机排序
random.seed([x]) # 改变随机数生成器的种子,可以在调用其他随机模块函数之前调用此函数
2.4 其他类型
空值: None
注意: None不可理解为0,0有实际意义,而None为【特殊空值】代表”无“。
2.5 结构定义
python中有列表、元组、集合、字典这四种可以存放多个元素的集合,总体功能上都起着存放数据的作用,但彼此之间都存在着区别。
2.6 列表
列表(list)用来存储不同的数据类型,使用中括号"[]"。列表中存储的元素可变,对应操作均会影响原列表。
2.6.1 列表操作符
1)标准类型操作符
比较操作符:依次比较两列表中的元素,直到某一方”胜出“。
2)序列类型操作符
list_1 = ['list', 'tuple', 'dict']
list_2 = ['sets']
# 索引操作
print(list_1[1]) # 输出列表中的第二个元素
print(list_1[-1]) # 输出列表中的最后一个元素
Out:
tuple
dict
"""
由于编程语言中的序列都是从0开始计数的,正常排列下第一个元素用代码表示应为list_1[0]
"""
# 切片操作
print(list_1[1:]) # 输出列表中第二个元素至最后一个元素(包括第二个元素)
print(list_1[:-1]) # 输出列表中最后一个元素之前的内容(不包括最后一个元素)
print(list_1[::-1]) # 倒序输出
Out:
['tuple', 'dict']
['list', 'tuple']
['dict', 'tuple', 'list']
"""
在python中数据结构的范围一般采取“左闭右开”的取值方式,即包括左边数值不包括右边数值。
"""
# 重复操作
print(list_1 * 3) # 输出三次
Out:
['list', 'tuple', 'dict', 'list', 'tuple', 'dict', 'list', 'tuple', 'dict']
# 检查操作
print('sets' in list_1)
print('sets' not in list_1)
Out:
False
True
# 连接操作
print(list_1 + list_2) # 不推荐,直接输出不影响list_1中的内容
list_1.extend(list_2) # extend函数用于在列表末尾添加另一序列的元素,会改变列表中的元素
print(list_1)
Out:
['list', 'tuple', 'dict', 'sets']
['list', 'tuple', 'dict', 'sets']
# 列表嵌套
list_3 = [['list', 'tuple', 'dict'],
[1, 2, 3],
['a', 'b', 'c']]
print(list_3[2][2]) # 输出第三个元素组中的第三个元素
print(list_3[:][1]) # 输出第二个元素组
print(list_3[:][1][2]) # 输出第二个元素组的第三个元素
print(list_3[2][1:]) # 输出第三个元素组的第二个至最后一个元素
print(list_3[1:][1]) # 输出第三个元素组
Out:
c
[1, 2, 3]
3
['b', 'c']
['a', 'b', 'c']
"""
嵌套列表可以理解为一个矩阵,第一个中括号为行序号,第二个中括号为列序号;
实际上 : 的作用在于分割,例2中 : 并未给定分割范围,因此分割后表现依旧为原列表,但实际上是一个新列表。这个新列表将原列表中包含的三个列表看作三个元素整体,因此第二个中括号只指定输出某一个列表,如果需要输出某一个元素则需要第三个中括号,如例3;
例4中第一个中括号指定了第三个元素组,第二个中括号负责分割,输出该元素组第二个至最后一个元素;
例5中第一个中括号完成了切割操作,此时新列表中内容为[[1, 2, 3], ['a', 'b', 'c']],第二个中括号指定输出新列表第二个元素组。
"""
2.6.2 相关函数
2.6.2.1 序列类型内建函数
在python 3.x版本中,为了节约内存将一些函数的返回值改为了对象(内存地址),需要使用**list()**函数转化为列表。
len() sum() max() min() # 返回长度、总和、最大值、最小值
sorted() list(reversed()) # 顺序排序列表 按原顺序逆序列表(不进行排序)
list_1 = ['list', 'tuple', 'dict']
for i, element in list(enumerate(list_1)):
print(i, element)
Out:
0 list
1 tuple
2 dict
"""
list(enumerate())函数可以将一个可迭代序列转换成一组索引,包含数字下标以及元素值
"""
list_1 = [1, 2, 3]
list_2 = [4, 5, 6, 7]
list_3 = list(zip(list_1, list_2))
print(list_3)
print(list(zip(*list_3)))
Out:
[(1, 4), (2, 5), (3, 6)]
[(1, 2, 3), (4, 5, 6)]
"""
zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。
如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同,利用 * 号操作符,可以将元组解压为列表。
"""
2.6.2.2 列表类型函数
list.count() # 统计某个元素在列表中出现的次数
list.index() # 查找某个值第一个匹配元素的下标
list.append() # 将元素添加至末尾
list.insert(num, element) # 插入元素到索引号为【num】的位置
list_1.extend(list_2) # 将列表二的所有元素插入到list_1的末尾
list.pop(i) # 删除指定位置【i】的元素,若为空则删除末尾元素
list.remove() # 移除列表中某个值的第一个匹配项
list.sort() # 列表排序
list.reverse() # 列表逆序
注意: 用于改变对象值的【可变对象方法】,直接对【原对象】进行修改,并没有返回值。
list_1 = ['list', 'tuple', 'dict']
list_2 = list_1.reverse()
print(list_2)
print(list_1)
Out:
None
['dict', 'tuple', 'list']
2.7 元组
元组(Tuple)和列表类似,两者的区别在于元组的元素一旦初始化则不能进行修改,其指向永远不变,使用小括号“()”。
2.7.1 相关函数
序列类型内建函数与列表类似,但由于已定义元组元素不能修改,元组类型函数只有两个。
tuple.count() # 统计某个元素在元组中出现的次数
tuple.index() # 查找某个值第一个匹配元素的下标
2.7.2 元组特性
1)元组不可变中的“可变”
tuple_1 = ('list', 'tuple')
tuple_2 = ('dict', 'sets')
tuple_1 = tuple_1 + tuple_2
print(tuple_1)
Out:
('list', 'tuple', 'dict', 'sets')
"""
如上可达到修改的目的,但本质上是新建了对象。
"""
tuple_1 = ('list', ['tuple', 'dict'])
tuple_1[1].append('sets')
print(tuple_1)
Out:
('list', ['tuple', 'dict', 'sets'])
"""
若元组中包含list元素,list元素是可变的,但对于元组指向的list不变。
"""
2)默认集合类型
一般来说,多对象、逗号分割的、没有明确符号定义的一组数据其默认集合类型均为元组,例如函数返回的多对象等。
因此建议总是【显示】运用【圆括号表达式】来表示元组,如单元素元组 tuple = (1, ) 其中的逗号用来消除小括号带来的歧义。
除此之外,元组还可以作为字典的键。
2.8 拷贝问题
拷贝可以分为浅拷贝和深拷贝两种,在讨论拷贝之前先复习一下可变数据类型和不可变数据类型。
可变数据类型:value改变,id不变,e.g. 列表、字典;
不可变数据类型:value改变,id也随之改变,e.g. 数字、元组、字符串。
如果数据是不可变数据类型,当将其传给一个不了解的API时,可以确保其值不会被修改。如果需要操作一个从函数返回的元组,可以使用内建函数 list() 将其转化为列表(列表也可以通过 tuple() 转化为元组)。
2.8.1 浅拷贝
新建一个与原对象相同类型的对象,该对象中元素是对原对象中元素的引用,但两个对象的内存地址不同(可以看作‘表头’不同)。新对象和原对象之间存在关联但不是同一个对象。拷贝序列类型对象(列表、元组)时,默认使用浅拷贝。
import copy
list_1 = ['list', 'tuple', 'dict', ['sets']]
# 1) 完全切片 [:]
list_2 = list_1[:]
# 2) copy模块
list_3 = copy.copy(list_1)
print(id(list_1), id(list_2), id(list_3), '\n')
zipped = list(zip(list_1, list_2, list_3))
for i in range(len(zipped)):
for j in range(len(zipped[i])):
print(id(zipped[i][j]))
print('\n')
Out:
2338658258880 2338661222528 2338658387904
2340676704176
2340676704176
2340676704176
2340676704688
2340676704688
2340676704688
2340676703728
2340676703728
2340676703728
2338661125888
2338661125888
2338661125888
"""
可以看出三个列表的内存地址不同,但其中元素的内存地址是相同的。如果list_1中的元素改变了,对list_2、list_3是否产生影响?
"""
list_1[0] = 'None'
print(list_1, '\n', list_2, '\n', list_3, '\n')
list_1.append('None')
print(list_1, '\n', list_2, '\n', list_3, '\n')
list_1[3].append('None')
print(list_1, '\n', list_2, '\n', list_3, '\n')
Out:
['None', 'tuple', 'dict', ['sets']]
['list', 'tuple', 'dict', ['sets']]
['list', 'tuple', 'dict', ['sets']]
['None', 'tuple', 'dict', ['sets'], 'None']
['list', 'tuple', 'dict', ['sets']]
['list', 'tuple', 'dict', ['sets']]
['None', 'tuple', 'dict', ['sets', 'None'], 'None']
['list', 'tuple', 'dict', ['sets', 'None']]
['list', 'tuple', 'dict', ['sets', 'None']]
"""
为什么直接向list_1添加字符串“None”后,list_2、list_3不会产生变化;而向list_1嵌套列表中添加字符串“None”后,list_2、list_3的值也随之改变了呢?
回顾不可变类型和可变类型,由于字符串属于不可变类型,在添加或修改时实际上产生了一个新的对象;而嵌套列表属于可变类型,修改和添加操作不会产生新的对象,且原本拷贝的就是嵌套列表的‘表头’地址(可以回想一下第一章的可变对象图),所以list_1的修改也会同时反应到list_2和list_3上。
可以理解为向三个仓库运送来源相同的货物,其中有一种货物为组合款。直接添加字符串相当于其中一个仓库私自多订了一种货物,而向嵌套列表中添加字符串相当于仓库要求出货商对组合款货物进行修改,而出货商原本就需要对三个仓库发放相同的组合款货物,因此也需要同时发放修改后的组合款货物。
"""
2.8.2 深拷贝
简单来说,深拷贝就是在浅拷贝新建对象的基础上,将原对象的元素也重新创建一份,此时新对象和原对象没有任何关系。
import copy
list_1 = ['list', 'tuple', 'dict', ['sets']]
list_2 = copy.deepcopy(list_1)
print(id(list_1), id(list_2), '\n')
zipped = list(zip(list_1, list_2))
for i in range(len(zipped)):
for j in range(len(zipped[i])):
print(id(zipped[i][j]))
print('\n')
Out:
2338661223936 2338661222336
2340676704176
2340676704176
2340676704688
2340676704688
2340676703728
2340676703728
2338661267200
2338661268288
"""
可以看出深拷贝之后的新对象和原对象‘表头’内存地址不同,且其中元素的内存地址也不同。
"""
2.8.3 拷贝特殊情况
1) 对于【非容器类型】+ 字符串类型没有拷贝一说
也就是数字、字符串以及其他原子类型对象不存在拷贝,拷贝即“赋值”
import copy
string_1 = "string_1"
string_2 = copy.copy(string_1)
string_3 = copy.deepcopy(string_1)
print(id(string_1), id(string_2), id(string_3))
print(string_2 is string_1)
print(string_3 is string_1)
Out:
2338658386992 2338658386992 2338658386992
True
True
2) 对于【元组】类型不存在浅拷贝
如果其中元素都是原子类型元素,则也不存在深拷贝(效果等同于赋值)
import copy
tuple_1 = ('list', 'tuple', 'dict')
tuple_2 = copy.copy(tuple_1)
tuple_3 = copy.deepcopy(tuple_1)
print(id(tuple_1), id(tuple_2), id(tuple_3))
tuple_1 = ('list', 'tuple', 'dict', [])
tuple_2 = copy.copy(tuple_1)
tuple_3 = copy.deepcopy(tuple_1)
print(id(tuple_1), id(tuple_2), id(tuple_3))
Out:
2338660916416 2338660916416 2338660916416
2338660996416 2338660996416 2338661372128
2.8.4 赋值、浅拷贝与深拷贝
**赋值: ** 只传递对象的引用,相当于给对象再起一个名字,因此当对原变量操作时,结果会同步到新变量。
**浅拷贝: **创建了新的对象,但其中元素的引用和原对象相同。如果对原对象元素中不可变类型元素修改,不会反映到新对象上;而对原对象元素中的可变类型元素修改,相应的修改也会产生在新对象上。不可变数据类型没有浅拷贝(等同赋值)。
**深拷贝: **创建了新的对象,同时也创建了一组新的元素,新对象和原对象没有任何关系,在原对象上不管做什么修改都不会反应到新对象上。数字、字符串等原子数据类型没有深拷贝(等同赋值);元组中含有可变类型元素时才有深拷贝,否则深浅拷贝都没有。
2.9 字典
作为python中唯一内建的映射类型,字典(Dictionary)是通过名字来引用值的数据结构,并将这种数据结构称为映射。字典中元素的值(Value)没有特殊的顺序,都存储在一个特定的键(Key),键可以是数字、字符串或元组,使用大括号“{}”。
字典由多个键及其对应的值构成键值对,键和值之间以冒号隔开,项之间用逗号隔开。
2.9.1 字典函数
# 创建字典
dict_1 = {'list':'structure', 'tuple':'structure', 'dict':'structure'}
dict_2 = {}.fromkeys((list, tuple, dict), 'structure')
dict_3 = dict(list='structure', tuple='structure', dict='structure')
dict_4 = dict(zip(['list', 'tuple', 'dict'], ['structure', 'structure', 'structure']))
dict_5 = dict([('list', 'structure'), ('tuple', 'structure'), ('dict', 'structure')])
print(dict_1, '\n', dict_2, '\n', dict_3, '\n', dict_4, '\n', dict_5)
Out:
{'list': 'structure', 'tuple': 'structure', 'dict': 'structure'}
{<class 'list'>: 'structure', <class 'tuple'>: 'structure', <class 'dict'>: 'structure'}
{'list': 'structure', 'tuple': 'structure', 'dict': 'structure'}
{'list': 'structure', 'tuple': 'structure', 'dict': 'structure'}
{'list': 'structure', 'tuple': 'structure', 'dict': 'structure'}
# 访问字典
dict.keys() # 访问字典中的键
dict.values() # 访问字典中的值
dict.items() # 以元组形式输出键值对
dict_1.update(dict_2) # 将字典dict_2的键值对更新至dict_1
dict.copy() # 浅拷贝
# 判断键是否存在
print(key in/ not in dict)
dict.get(key, -1) # 返回Value或指定值,如果指定值为空则默认返回None
dict.setdefault(key, None) # 查找键,如果没有则添加对应键,键对应默认值为None
# 删除键
dict.pop(key) # 返回所删除键的对应值
dict.popitem() # 删除字典中最后一个元素,以元组形式返回键值对
del dict[key]
# 清空字典
dict.clear()
del dict
2.9.2 相关内建函数(BIF)
type()
str() # 转换成字符串
dict() # 工厂函数 当参数为容器类型对象时,要求必须成对出现且可迭代;当参数为另一个映射对象时,使用浅拷贝方式
len() # 返回键值对个数
hash() # 判断是否【可哈希】,只有可哈希才能作为字典的键
"""
如果一个对象在自己的生命周期中有一哈希值(hash value)是不可改变的,那么它就是可哈希的(hashable)的,因为这些数据结构内置了哈希值,每个可哈希的对象都内置了__hash__方法,所以可哈希的对象可以通过哈希值进行对比,也可以作为字典的键值和作为set函数的参数。
所有python中所有不可改变的的对象(imutable objects)都是可哈希的,比如字符串,元组,也就是说可改变的容器如字典,列表不可哈希(unhashable)。我们用户所定义的类的实例对象默认是可哈希的,它们都是唯一的,而hash值也就是它们的id()。
"""
2.9.3 字典、列表对比
字典是一种典型的【以空间换时间】的数据结构,其查找速度极快且并不会随Key数量增加而变慢,缺点在于需要占用大量内存;
列表的查找方式主要是按照顺序进行查找,速度会随着元素数量的增加而变慢,但占用内存更少。
2.10 集合
集合(Set)的定义是一组Key的集合,无序排列且要求可哈希,其中存储顺序跟元素添加顺序不一致。存储元素不重复,不支持索引、重复、连接以及切片操作,使用大括号“{}”。
集合分为可变集合(Set)和不可变集合(Frozenset)两种,不可变集合通常用于嵌套集合,由于集合中通常不能包含集合等可变值,可以将内部集合设置为不可变集合,其主要缺点是初始化后无法向其中添加或删除元素。
# 创建集合
set_1 = set()
set_2 = set('abcde')
set_3 = set(['list', 'tuple', 'dict', 'set'])
set_4 = {'list', 'tuple', 'dict', 'set'}
print(set_1, '\n', set_2, '\n', set_3, '\n', set_4, '\n')
Out:
set()
{'c', 'd', 'b', 'a', 'e'}
{'list', 'set', 'dict', 'tuple'}
{'list', 'set', 'dict', 'tuple'}
"""
要注意区分集合和字典,example = {} 这样的初始化创建的是字典不是集合!
"""
2.10.1 集合操作符
1)标准类型操作符
in / not in 、<、<=、>、>=、!=、== 用来判定元素和集合、集合和集合之间的关系。
2)集合类型操作符
# 并集 OR
set_1 | set_2 set_1.union(set_2)
# 交集 AND
set_1 & set_2 set_1.intersection(set_2)
# 差集 C
set_1 - set_2 set_1.difference(set_2) # 包含在set_1中且不包含在set_2中的元素
# 对称差分 XOR
set_1 ^ set_2 set_1.symmetric_difference(set_2) # 并集减去交集
2.10.2 相关函数
# 添加元素
set.add(key)
set.update(seq) # 用于添加多个元素
# 删除元素
set.remove(key) # 删除单个值 缺点:删除集合中不存在的值时会报错
set.discard(key) # 和字典中的dict.get()函数类似,系统不会产生【KeyError】
set.pop() # 从集合中删除任意一个值
# 清空集合
set.clear()
# 进行迭代
for element in set:
print(element)
# 集合排序
sorted(set) # 返回一个有序排列的列表
# 嵌套集合
set_1 = frozenset('abc')
set_2 = set([set_1])
print(set_1, '\n', set_2)
Out:
frozenset({'a', 'c', 'b'})
{frozenset({'a', 'c', 'b'})}
2.10.3 集合功能
集合的存在主要是为了删除列表中的重复元素。
# 使用集合删除列表重复值
print(list(set([1, 2, 3, 2, 1])))
"""
除此之外,可以使用列表推导式从列表中删除重复值:
unique = []
[unique.append(element) for element in list if element not in unique]
对比两种方法,使用集合删除重复值更加高效,节约时间。
"""
2.10.4 集合、元组对比
集合排列是无序的,可以看作【无Value的字典】;
元组排列是有序的,可以看作【不可变的列表】。
# 添加元素
set.add(key)
set.update(seq) # 用于添加多个元素
# 删除元素
set.remove(key) # 删除单个值 缺点:删除集合中不存在的值时会报错
set.discard(key) # 和字典中的dict.get()函数类似,系统不会产生【KeyError】
set.pop() # 从集合中删除任意一个值
# 清空集合
set.clear()
# 进行迭代
for element in set:
print(element)
# 集合排序
sorted(set) # 返回一个有序排列的列表
# 嵌套集合
set_1 = frozenset('abc')
set_2 = set([set_1])
print(set_1, '\n', set_2)
Out:
frozenset({'a', 'c', 'b'})
{frozenset({'a', 'c', 'b'})}
2.10.3 集合功能
集合的存在主要是为了删除列表中的重复元素。
# 使用集合删除列表重复值
print(list(set([1, 2, 3, 2, 1])))
"""
除此之外,可以使用列表推导式从列表中删除重复值:
unique = []
[unique.append(element) for element in list if element not in unique]
对比两种方法,使用集合删除重复值更加高效,节约时间。
"""
2.10.4 集合、元组对比
集合排列是无序的,可以看作【无Value的字典】;
元组排列是有序的,可以看作【不可变的列表】。