Python中,变量的值分为可变(mutable)或 不可变(immutable),该特征决定了变量在赋值、修改时的行为和性能。
不可变类型的值一旦创建,就无法改变。任何试图修改不可变对象的操作都会创建一个新的对象。常见不可变类型包括:
数字的值一旦赋值,不能被修改。
x = 10
print(id(x), hex(id(x)))
x = x + 5 # 会创建一个新的整数对象,并将其赋值给 x
print(id(x), hex(id(x)))
# 两次id不一样
# id():获取变量引用的内存地址;
# hex():转换为16进制;
字符串是不可变的,任何试图修改字符串内容的操作都会生成一个新的字符串。
s = "hello"
print(id(s))
s = s + " world" # 创建一个新的字符串对象并赋值给 s
print(id(s))
元组是不可变的容器类型,一旦创建,其内容不能更改。
t = (1, 2, 3)
t[0] = 10
# 会抛出 TypeError: 'tuple' object does not support item assignment
frozenset 是不可变版本的集合,一旦创建,无法修改其元素。
fs = frozenset([1, 2, 3])
fs.add(4)
# 会抛出 AttributeError: 'frozenset' object has no attribute 'add'
布尔值 True
和 False
也属于不可变类型。
flag = True
flag = False
# 这不是修改,而是创建了一个新的布尔值并赋值给flag
字节串和None为不可变类型。
可变类型的值在创建后可以被修改。常见的可变类型:
列表是可变的,可以在原地修改其元素、添加或删除元素。
lst = [1, 2, 3]
lst[0] = 10 # 修改原列表的元素
lst.append(4) # 添加新的元素
字典也是可变类型,可以修改其键值对。
d = {'a': 1, 'b': 2}
d['a'] = 10 # 修改字典中键 'a' 对应的值
d['c'] = 3 # 添加新的键值对
集合是可变的,可以添加或删除元素。
s = {1, 2, 3}
s.add(4) # 添加元素
s.remove(2) # 删除元素
自定义类的实例如果没有在类中明确限制其属性,可以修改实例的属性值,因此对象实例也是可变的。
class MyClass:
def __init__(self):
self.value = 0
obj = MyClass()
obj.value = 10 # 修改对象的属性
对不可变类型改值时,Python会创建一个新的对象,并将该对象的引用赋给原来的变量。
对可变类型修改内容时,不会创建新的对象,而是直接在原地进行修改。
import copy
# 这里是重新赋值,不是对内存空间里面的值进行修改
scoresB = [5, 6, 7, 8, 9]
print(scoresA)
scoresA = [1, 2, 3, 4, [100, 200, 300]]
# 浅拷贝:复制scoreA,不再是直接引用
scoresB = scoresA.copy()
print(id(scoresA), id(scoresB))
scoresA[4][1] = 666
print(scoresA[4][1]) # 666
print(scoresB[4][1])
# 深拷贝:完全拷贝,不会改变原数据
scoresA = [1, 2, 3, 4, [100, 200, 300]]
scoresB = copy.deepcopy(scoresA)
scoresA[4][1] = 666
print(scoresA[4][1])# 666
print(scoresB[4][1])
不可变对象的引用在修改后总会指向一个新对象,而可变对象的引用在修改时仍然指向原始对象。
# 不可变类型
a = 100
b = a
a = 200
print(a, b)
# 可变类型
scoresA = [1, 2, 3, 4]
scoresB = scoresA
scoresA[3] = 666
print(scoresB)
不可变类型具有固定的哈希值,可以作为字典的键。
可变类型哈希值可能变化,不能作为字典的键。
d = {}
d[(1, 2)] = "tuple key" # 元组是不可变的,可以作为字典的键
d[[1, 2]] = "list key" # 会抛出 TypeError: unhashable type: 'list'
特别注意
以上是基于修改而不是赋值,重新赋值操作都会指向新的对象引用。
使用isinstance判断类型变量
a = 1
print(isinstance(a, int))
# 判断是否属于指定类型
# True
str1 = 'abc'
print(isinstance(str1, (int, str)))
# 判断是否属于多个类型之一
# True
这里只是总结一下常见类型转换的函数:
函数 | 说明 | 示例 |
---|---|---|
int(x) | 将 x 转换为整数类型。 |
int("10") → 10 |
float(x) | 将 x 转换为浮点数类型。 |
float("3.14") → 3.14 |
str(x) | 将 x 转换为字符串类型。 |
str(123) → "123" |
list(x) | 将 x 转换为列表类型,x 必须是可迭代对象。 |
list((1, 2, 3)) → [1, 2, 3] |
tuple(x) | 将 x 转换为元组类型,x 必须是可迭代对象。 |
tuple([1, 2, 3]) → (1, 2, 3) |
set(x) | 将 x 转换为集合类型,x 必须是可迭代对象。 |
set([1, 2, 3]) → {1, 2, 3} |
dict(x) | 将 x 转换为字典类型,x 必须是可迭代的键值对序列(如列表、元组)。 |
dict([('a', 1), ('b', 2)]) → {'a': 1, 'b': 2} |
bool(x) | 将 x 转换为布尔类型,x 的值可以是任何对象,返回 True 或 False 。 |
bool(0) → False ; bool("abc") → True |
frozenset(x) | 将 x 转换为冻结集合,x 必须是可迭代对象。 | frozenset([1, 2, 3]) → frozenset({1, 2, 3}) |
bytes(x) | 将 x 转换为字节串类型,x 可以是字符串、字节数组等。 | bytes("hello", "utf-8") → b'hello' |
complex(x) | 将 x 转换为复数类型。 | complex(2) → (2+0j) |
chr(x) | 将整数 x 转换为对应的字符,x 是 Unicode 码点。 | chr(97) → 'a' |
ord(x) | 将字符 x 转换为对应的整数,x 是一个单一字符。 | ord('a') → 97 |
eval(x) | 将字符串 x 作为 Python 表达式进行求值并返回结果。 | eval('3 + 4') → 7 |
set(x) | 将可迭代对象 x 转换为集合。 | set([1, 2, 2, 3]) → {1, 2, 3} |
注意事项:
并不是所有类型都可以直接转换。如:
int("hello")
# 会抛出 ValueError: invalid literal for int() with base 10: 'hello'
类型转换可能会引发错误或丢失信息,比如浮点数转换为整数时,小数部分会丢失。