元组数据类型
1. 元组数据类型
元组表示有序不可变元素的集合,元素可以是任意数据类型,可以说元组就是不可变的列表。
定义:元组是通过一对小括号进行包裹,元素之间使用逗号隔开。
a = () # 空元祖
b = ("a", "b", "cde") # 字符串
c = (1, "b", "c") # 数字
d = (1, "b", []) # 列表
e = (1, "b", (2, "c")) # 元祖的嵌套
f = 1,2,3 # 会自动把后面的数形成一个元组然后交给 f,这就是一种语法。
print('a的类型为:', type(a)) # a的类型为: <class 'tuple'>
print('b的类型为:', type(b)) # b的类型为: <class 'tuple'>
print('c的类型为:', type(c)) # c的类型为: <class 'tuple'>
print('d的类型为:', type(d)) # d的类型为: <class 'tuple'>
print('e的类型为:', type(e)) # e的类型为: <class 'tuple'>
print('f的类型为:', type(f)) # f的类型为: <class 'tuple'>
python中的元组用tuple表示
特殊的是单个元素的元组
g = ('1')
h = ('1',)
print('g的类型为:', type(g)) # g的类型为: <class 'str'>
print('h的类型为:', type(h)) # h的类型为: <class 'tuple'>
j = ('hello')
k = ('hello',)
print(type(j)) # <class 'str'>
print(type(k)) # <class 'tuple'>
g的类型为: <class ‘str’>
h的类型为: <class ‘tuple’>
注意:单元素元组的定义,一定要多加个逗号。不加逗号表示的是单元素本身,不是元组。
补充
>>> [1,]
[1]
>>> {1,}
{1}
>>> (1,)
(1,)
>>> (1)
1
单个元素的列表,集合后面也可以加一个逗号,不会报错。但是单个元素的元组必须多加一个逗号不然数据类型就是这个单元素本身的类型。
2. 元组的索引和切片
序列的索引和切片完全一致,参见字符串。
t1 = (1, 2, 3, 4)
print(t1[0]) # 1
print(t1[-1]) # 4
print(t1[::-1]) # (4, 3, 2, 1)
注意:元组不可变,上面的操作都没有改变t1,只是把t1里面的值取出来。元组切片出来还是元组(序列的切片,序列是什么类型切出来就是什么类型)
3. 元组的常用操作
元组的元素不能修改,增加和删除,其他操作和列表的操作一致。
元组利用不可修改的特性,应用在多变量赋值和函数多返回值上
a , b = (1, 2) # 本质上等于 a = 1 b = 2 右边也可以写成其他可迭代对象,只是带括号的常用一点,但是括号也可以省略。
经常简写为 a, b = 1, 2 把1赋值给a,把2赋值给b
print(a) # 1 这叫元组的多变量赋值
print(b) # 2
注意:单行多变量赋值,变量后的值的个数一定要和变量的数量相匹配,多了少了都不可以。
需求: a , b = 1, 2 print(a,b) 如果这个时候想要把a的值给b,b的值给a该怎么做。
c = a # c = 1
a = b # a = 2
b = c # b = 1
快捷语法
a, b = b, a 表示把b给a,把a给b # 经常用,记住
print(a,b)
4. 元祖的常用方法
元组只有两个公有方法,count 和 index 用法与列表相同
print(dir(())) # 只有 count和index方法 用法同 list
5. len函数
内建函数len可以获取对象中包含的元素的个数
字符串:字符的个数
列表:元素的个数
元组:元素的个数
s = 'abcdef'
s_l = len(s)
print(s_l) # 6
ls = [1, 2, 3]
ls_1 =len(ls)
print(ls_1) # 3
t = (1, 2, 3, 4)
t_1 = len(t)
print(t_1) # 4
tup = (1, 2, 3,(4,5)) # [4, 5] 整体是 tup的一个元素
print(len(tup)) # 4
6. 可变和不可变对象
在讲可变不可变之前我们先讲一个id()函数
id内置函数,
返回传入对象的虚拟内存地址的整数值。
a = 1
print(a)
print(id(a)) # 1 140727716184336 虚拟内存地址,一个大整数。
id函数具体是怎么做的:
你碰到一个赋值语句 把1赋值给a它的过程是什么样呢? a = 1
它首先会在内存里面找一个地方,在这个地方放我的值1,然后会把变量a指向1所在的位置而不是指向值,变量a其实是引用1所在的位置而不是这个值本身。所以当我去 id(a)的时候,拿到的是a里面所指向的内存地址。可以想象成在宾馆通过房号来找人。
python中的对象根据内存机制分为可变和不可变两种
可变对象
可变对象可以在其id()保持固定的情况下改变其取值。
下面的列表a,修改值后,id保持不变。
>>> a = [1, 2, 3]
>>> id(a) # id内置函数,返回传入对象的虚拟内存地址的整数值。
2273926861832 # 虚拟内存地址,一个大整数
# 修改a的值
>>>a[0] = 'a'
>>>a
['a', 2, 3]
>>>id(a)
2273926861832 # 修改值后 id 不变
那么它在内存中的处理是怎么样的呢?为了更好地理解,我们画一个图
对于可变数据类型而言里面的元素放的不是值,放的也是引用,把1 改为 'a’只是把引用改了,这个时候第一个位置就不会引用1了,会重新引用‘a’的内存地址,但是a所指向的地址是始终不变的,所以在其id固定不变的情况下改变了其取值,是可变数据类型。
如果在修改变量时,不需要开辟新的内存空间,这样的数据类型称为可变类型
基本数据类型中,列表,集合,和字典都是可变数据类型(我变了,但是我的id没有变)
不可变对象
如果修改一个对象的值,必须重新一个创建新的对象,那么这个对象就是不可变类型。
例如下面的字符串s,修改后id发生了改变,
s = 'hello'
print(id(s)) 2470237384752
s = 'HeLLO' # 字符串没办法去改变它的某一个元素,要改的话只能去重新赋值。
print(id(s)) 2470237408496 # 修改内容后 id 发生了改变
修改前后内存地址不同,说明修改时,又重新开辟了一块内存空间在里面放了一个'Hello'并且把新的内存地址指向a,说明原来的数据是不可变的。
基本数据类型中的数值(int,float,complex),字符串,元组是不可变对象。(赋值好之后不能修改,如果想修改只能重新创建一个)
注意:元组里面有列表也是可以改变列表的,你改的是元素,而不是元组。
怎么判断可变不可变:
- 通过内置函数hash,凡是可以被hash的就是不可变,凡是不可以被hash的就是可变
- 判断能否修改,能修改的就是可变类型,不能修改的就是不可变对象
- 判断修改前后 id 是否变化
7. 可哈希对象
可哈希:凡是可以通过hash这个函数进行求值的就是可哈希对象
>>> hash(1)
1
>>> hash('1.2')
-3694403444746463426
>>> hash((1, 2, 3))
2528502973977326415
>>> hash([1, 2])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>>
总结:
不可变类型可以被hash
可变类型不能被hash
可哈希对象:
①、能否被hash函数运算
②、不可变数据一定是可哈希的
③、可变数据一定不可哈希
8 赋值与深浅拷贝
8.1 赋值
python是解释型编程语言,当解释器在碰到赋值语句时它首先会先计算赋值符号右边的表达式的值,然后再创建左边的变量。
变量中实际存储的是值在内存中的地址,引用变量时通过地址指向内存中的值,通过内建函数id可以查看解释器中变量的虚拟内存地址的整数值。python中的变量是引用,变量中存的不是值,存的是值的位置(内存地址) --举例 在宾馆通过房号来寻找对应的人
变量赋值与可变类型
>>>a = [1, 2, 3]
>>>id(a)
1369689575944
python中的赋值语句不是复制对象,而是创建目标和对象的绑定关系,所以将一个变量赋值给另一个变量时,并不会创建新的值,只会让被赋值的变量指向相同的内存地址,但是如果我们要去修改赋值后的变量,对于可变类型于不可变类型就会有区别:
>>> a = [1, 2, 3]
>>> b = a
>>> id(a)
1803699115016
>>> id(b)
1803699115016
对于上面这个内容我们画一个内存图来帮助我们理解
代码从上往下运行,当碰到第一句的时候,会在内存里面开辟一块内存空间,存放[1,2,3] 这个列表,然后a指向这个列表的地址,而不是值,这个地址里面放了[1,2,3],接下来 a又赋值给b,把a赋值给b其实就是让b也指向a所指向的地址,所以这个时候b也指向这个地址。所以b和a的id是一样的。
这个时候修改a会影响b修改b也会影响a
>>>a
[1, 2, 3]
>>>b
[1, 2, 3]
>>>b[0] = 'a'
>>>b
['a', 2, 3]
>>>a
['a', 2, 3]
>>> id(a)
1803699115016
>>> id(b)
1803699115016
对于可变数据类型来说,改a就会影响b,改b就会影响a。
变量赋值与不可变类型
>>> a = 1
>>> b = a
>>> id(a)
140713833300240
>>> id(b)
140713833300240
>>> a += 1
>>> a
2 # 整数是不可变类型,所以现在想给a赋值一个2,需要重新创建一个2
>>> b
1
>>> id(a)
140713833300272
>>> id(b)
140713833300240
>>>
对于不可变数据类型来说,我们去把a赋值给b,然后去修改a的时候对b是没有影响的,因为是不可变类型,所以会重新开辟内存空间里面放一个2,然后把2的内存地址指向a。
8.2 深浅拷贝
浅拷贝
浅拷贝构造一个新的复合对象,然后(在尽可能的范围内)将原始对象中找到的对象的引用插入其中。
导入copy模块中的copy函数就是浅拷贝操作
- 对于不可变数据类型
import copy
a = 300
s = 'hello'
b = copy.copy(a) # 给a浅拷贝一个副本叫b
d = copy.copy(s) # 给s浅拷贝一个副本叫d
print(id(a),id(b))
(2196525001616, 2196525001616)
print(id(s),is(d))
(2196525603696, 2196525603696)
b和a的id相等,d和s的id相等,这个等价于把a赋值给b,把s赋值给d
对于字符串数字这种不可变数据类型来说,浅拷贝就相当于变量赋值,会让它们指向相同的引用,所以拷贝前后的id是相等的。
a += 1
print(a) # 301 对于不可变数据类型来说,我要修改一个变量等于我重新生成一个值。
print(b) # 300
对原变量的修改会创建新的值,不会影响浅拷贝生成的变量,变量a自加1后指向301,变量b的值不变。
- 对于可变数据类型
对于可变数据类型,列表,字典,集合等浅拷贝会有不一样的结果。
ls = ['a', 'b', 'c']
ln = copy.copy(ls)
print(id(ls),id(Ln)
1848641486152 1848641486216 # 复制它的内存地址,复制它的引用
ls[0] = 1
print(ls)
print(ln)
[1, 'b', 'c']
['a', 'b', 'c']
我们现在再把上面的列表换一下
ls = [1, '2',['a','b']]
ln = copy.copy(ls)
print(id(ls),id(ln))
2184334310920 2184334311048
为了更好的理解我们来画一个图
当对可变数据类型进行浅拷贝时,会创建一个新数据,所以变量ls和ln的id不相等。
浅拷贝主要针对可变数据类型,当浅拷贝碰见可变数据类型,它只会复制第一层,如果里面有嵌套的可变数据类型,它不会复制到可变数据类型里面更深的数据,它只会复制最外面那层引用,所以当我们做了浅拷贝的时候,把ls中的1或者’2修改’后不会影响ln中的值,但是ls把b改为了c之后,ln里面的b也会变成c,当我们做浅拷贝的时候,如果我们改变了ls1嵌套的可变数据,也会影响复制后的值。
当对可变数据类型进行浅拷贝时,会创建一个新的数据,所以变量ls和ln的id不相等。
但是ls和ln的嵌套列表的id是一样的。
print(id(ls[2])
print(id(ln[2])
2804547812744 2804547812744 # id 是相同的,说明第三个元素是同一个列表。是因为第三个元素引用的是原来列表的内存地址,所以对原来元素的修改会影响拷贝后的是数据。
ls[2][1] = c
print(ls)
print(ln)
[1, '2', ['a', 'c']]
[1, '2', ['a', 'c']]
修改ls中最后一个元素,因为是可变数据类型,所以ls中的值也发生了改变
深拷贝
不可变数据类型的深浅拷贝一致
进行深拷贝会对数据中的所有元素从里到外完全复制一份,不管有多少层嵌套。复制后修改任意一个对另外一个互不影响。
深浅拷贝实际上我们用不到,主要是为了应付面试。
浅拷贝的时候,它拷贝的是对象的引用,只会拷贝一层
深拷贝它会递归地去拷贝这个对象的每个元素的所有的副本
深拷贝可以理解为复制文件夹一样,会完全复制一份,就跟备份一份是一样的意思
简单这样去理解:比如说我们有一个文件夹1里面有一个文件,浅拷贝就是我再创建一个文件夹2,把文件夹1里面文件的快捷方式拷贝一份拷贝到文件夹2中,现在不管通过文件夹1还是文件夹2,改变其中一个文件里面的内容另外一个文件夹里面的文件也会改变。深拷贝就是直接把文件夹复制一份,改其中一个数据对另外一个根本没有影响。
import copy
ls = [1, 2, 3, ['a', 'b']]
ln = copy.deepcopy(ls)
ls[3][0] = 'c'
print(ls)
print(ln)
[1, 2, 3, ['c', 'b']]
[1, 2, 3, ['a', 'b']] # ls改了ln没有改
总结:
浅拷贝对于不可变类型来说就相对于变量赋值,赋值后如果要修改会重新开辟内存空间,不会影响原来的值。
浅拷贝对于可变数据类型来说,也会创建一个新的数据,他们的id不相同但是指向的值是相同的,修改会重新指向另外的数据,不会影响原来的值,但是如果浅拷贝前的列表里面有嵌套的列表,它引用的就是这个嵌套列表整体的id,浅拷贝后的列表引用的也是这个嵌套列表整体的id,此时如果改变嵌套列表的里面的值,则一定会影响拷贝后的内容。
进行深拷贝会对数据中的所有元素从里到外完全复制一份,不管有多少层嵌套,复制后修改任意一个对另外一个互不影响。