Python语言是一个以一切皆对象的面向对象的动态型语言。Python的对象可以根据其是否可以变化划分为可变对象和不可变对象。
对象类型
不可变对象(值类型)
- Numbers:数值类型
- int: 整型数
- float: 浮点数
- complex: 复数
- bool: 布尔类型
- str: 字符型
- tuple: 元组
- range: 范围对象
- frozenset: 不可变集合
- bytes: 不可变字节数组
可变对象(引用类型)
- list: 列表
- dict: 字典
- set: 可变集合
- bytearray: 可变字节数组
- 用户自定义类
在编码中,我们可以对如str
, int
等对象进行修改,那为什么这些类型还是不可变对象。
实际上,当我们对这些不可变对象进行操作时,我们更改的并不是变量指向对象具体的值,而是使变量重新指向了一个不可变对象。
a = 123456789
b = 123456789
print(f"id(a) = {id(a)}, id(b) = {id(b)}")
# id(a) = 140219002169648, id(b) = 140219002169648
1
2
3
4
5
6
|
a = 123456789
b = 123456789
print(f"id(a) = {id(a)}, id(b) = {id(b)}")
# id(a) = 140219002169648, id(b) = 140219002169648
|
如上我们可以看到,当Python变量指向相同不可变对象时,不同的变量实际上是引用的相同的对象。
a = 123456789
print(f"id(a) = {id(a)}")
a = 987654321
print(f"update_id(a) = {id(a)}")
# id(a) = 140504869153072
# update_id(a) = 140504869153008
1
2
3
4
5
6
7
8
|
a = 123456789
print(f"id(a) = {id(a)}")
a = 987654321
print(f"update_id(a) = {id(a)}")
# id(a) = 140504869153072
# update_id(a) = 140504869153008
|
如上我们可以看到,当Python修改不可变对象时,实际上只是改变的变量指向的对象,而不是原来的不可变对象。
不可变对象的特例
元组作为Python中的容器类型,其本身是不可变的,但容器中存储的对象不一定都是不可变对象。
考虑的一下这种情形:
a = (1, 2, 3, [1, 2, 3])
print(f"a = {a}")
print(f"id(a) = {id(a)}")
a[3].append(4)
print(f"update_a = {a}")
print(f"update_id(a) = {id(a)}")
# a = (1, 2, 3, [1, 2, 3])
# id(a) = 140188517471056
# update_a = (1, 2, 3, [1, 2, 3, 4])
# update_id(a) = 140188517471056
1
2
3
4
5
6
7
8
9
10
11
|
a = (1, 2, 3, [1, 2, 3])
print(f"a = {a}")
print(f"id(a) = {id(a)}")
a[3].append(4)
print(f"update_a = {a}")
print(f"update_id(a) = {id(a)}")
# a = (1, 2, 3, [1, 2, 3])
# id(a) = 140188517471056
# update_a = (1, 2, 3, [1, 2, 3, 4])
# update_id(a) = 140188517471056
|
可以看到,元组明明是不可变对象,但我们却对其值进行了修改。主要原因是,元组作为容器,内部存储的都是其保存数据的内存地址,我们对该地址上的数据进行了操作修改,但没有改变其内存地址本身,所以元组的值自然而然的就发生了变化。
所以,不可变对象的“值”不可以改变,但其组成对象的“值”可以进行修改。在处理不可变对象时,要注意考虑到这种情形。
Python变量
Python中的变量都是指针,因为Python的变量都是指针,所以Python变量上无类型限制的,它是可以指向任意对象的,Python对象只是保存了指向数据的内存地址。
不可变对象(值类型)
在Python中,因为值类型作为不可变对象,他们本身的值是不可以修改的。所以对值类型的修改实际上是让变量指向了新的对象,原始对象会被Python的GC回收
a = 1
b = a
a = 2
print(b)
# 1
1
2
3
4
5
6
|
a = 1
b = a
a = 2
print(b)
# 1
|
修改值类型的值,因为是让变量指向了新的对象,不会对原始对象的属性造成影响。
可变对象(引用类型)
在Python中,当修改引用类型即可变对象时,因为对象是可变的的,所以可以直接对引用的对象进行操作。因为引用对象是对该实例的内存空间上的值进行修改,所以当有多个变量引用同一个引用实例时,对一个变量的修改,其他引用该实例的变量也会发生相应的变化。
a = [1, 2, 3]
b = a
a = a.append(4)
print(b)
# [1, 2, 3, 4]
1
2
3
4
5
6
|
a = [1, 2, 3]
b = a
a = a.append(4)
print(b)
# [1, 2, 3, 4]
|
可以看到通过对变量a
进行操作,变量b
的值也发生了相应的变化。
参数传递
函数按照传值的方式分为:
- 值传递:把调用函数时传递的值赋值到形参当中,对形参的操作不会影响外部的实参变量。
- 引用传递:把实参引用的内存地址赋值给形参,当对该内存的值进行修改时,会相应的影响到外部实参变量。
参数传递方式还包括地址传递,在Python这种无指针的高级语言中,不考虑辨别区分地址传递这种类型
a = 123
b = [1, 2, 3]
print(f"id(a) = {id(a)}, id(b) = {id(b)}")
def test(a, b):
print(f"func id(a) = {id(a)}, id(b) = {id(b)}")
test(a, b)
# id(a) = 140362719137840, id(b) = 140362724287744
# func id(a) = 140362719137840, id(b) = 140362724287744
1
2
3
4
5
6
7
8
9
10
11
12
13
|
a = 123
b = [1, 2, 3]
print(f"id(a) = {id(a)}, id(b) = {id(b)}")
def test(a, b):
print(f"func id(a) = {id(a)}, id(b) = {id(b)}")
test(a, b)
# id(a) = 140362719137840, id(b) = 140362724287744
# func id(a) = 140362719137840, id(b) = 140362724287744
|
通过上面的代码,可以看到Python可变对象和不可变对象在传递参数时,都是传递的变量指向的内存地址而不是进行的值传递。
Python为了方便内存的管理,都是采用的引用传递。在传递参数时,都传递的是对应的内存地址,所以在Python中对可变对象的修改,会引起外部对象的改变。不可变对象因为其特性,对不可变对象的操作效果和值传递具有相同的效果。