在 Python 中存在 int、str、float、bool 等基本数据类型,也存在 list、tuple、set、dict 这样的容器数据类型。这些数据类型被划分为可变(mutable)和不可变(immutable)两种类型。理解可变和不可变类型在Python中的概念对于编写高效、可靠的代码至关重要。
1. 变量引用和值
首先,我们要明白引用的含义,如下图所示:
通过示意图,我们可以理解在 Python 中变量名和变量的值是分开存储的。修改变量名存储的地址就可以理解为修改变量 name 的引用,如下图所示:
变量 name 存储的变量地址由 0x167483 变成了 0x784557,这就是修改变量的引用。如果我们修改的行为如下图所示:
变量 name 指向的内存空间的值由 smith 变成了 trump,这是修改变量的值。通过这个过程,一定要了解修改变量的引用和修改变量的值是两种不同的操。理解这个,后面就可以很轻松理解可变和不可变类型的概念了。
2. 可变和不可变类型
可变类型:变量的值可修改,
不可变类型:变量的值不可修改。
再次强调:可变和不可变类型,指的是该类型变量的值是否可以修改,而不是引用。
# 1. 整型 def demo01(): a = 100 print(id(a)) # id 发生变化,属于不可变类型 a = 200 print(id(a)) # 2. 浮点型 def demo02(): a = 3.14 print(id(a)) # id 发生变化,属于不可变类型 a = 2.15 print(id(a)) # 3. 字符串类型 def demo03(): a = "hello world" print(id(a)) # id 发生变化,属于不可变类型 a = "hello python" print(id(a)) # 4. 布尔类型 def demo04(): a = True print(id(a)) # id 发生变化,属于不可变类型 a = False print(id(a)) # 5. 列表类型 def demo05(): a = [10, 20, 30] print(id(a)) # id 未发生变化,属于可变类型 a.append(100) print(id(a)) # 6. 字典类型 def demo06(): a = {'name': 100} print(id(a)) # id 未发生变化,属于可变类型 a['name'] = 200 print(id(a)) # 7. 集合类型 def demo07(): a = {100, 200} print(id(a)) # id 未发生变化,属于可变类型 a.add(100) print(id(a)) # 8. 元组类型,本身不支持元素修改,不可变类型
通过代码演示,我们清楚的知道了,在 Python 中基本数据类型 int、float、str、bool 以及容器类型 tuple 都是不可变类型,而容器类型 list 、dict、set 都是可变类型。
3. 函数参数传递
函数参数传递是指在编程中,将参数传递给函数以供函数使用的过程。参数可以是函数需要的任何信息,例如数值、字符串、对象等。在调用函数时,将参数传递给函数,函数会使用这些参数执行特定的操作,并可能返回结果。参数传递可以通过值传递或引用传递来实现,具体取决于编程语言和函数的定义方式。在值传递中,函数接收到的是参数的副本,而在引用传递中,函数接收到的是参数的引用,可以直接操作原始数据。
- 当可变对象作为函数参数传递时,实际上是将对象的引用传递给函数。
- 当不可变对象作为函数参数传递时,函数内部的修改不会影响原始对象。
- 当可变对象作为函数参数传递时,在函数内部的修改会影响到原始对象。
# 1. 函数传参方式:值方式、引用方式 def func01(my_val, my_lst): print('my_lst:', id(my_lst), 'my_val:', id(my_val)) def test01(): my_lst = [10, 20, 30] my_val = 100 print('my_lst:', id(my_lst), 'my_val:', id(my_val)) func01(my_val, my_lst) # 2. 不可变类型作为函数参数 def func02(param): param = 200 print('param:', id(param)) def test02(): num = 100 print('num:', id(num), num) func02(num) print('num:', id(num), num) # 3. 可变类型作为函数的参数 def func03(param): param.append(100) print('param:', id(param), param) # 对可变类型进行拷贝 import copy param = copy.deepcopy(param) print('copy param:', id(param), param) def test03(): my_lst = [10, 20, 30] print('my_lst:', id(my_lst), my_lst) func03(my_lst) print('my_lst:', id(my_lst), my_lst) if __name__ == '__main__': test01() print('-' * 30) test02() print('-' * 30) test03()
程序输出结果:
my_lst: 2558955322048 my_val: 140703537636096 my_lst: 2558955322048 my_val: 140703537636096 ------------------------------ num: 140703537636096 100 param: 140703537639296 num: 140703537636096 100 ------------------------------ my_lst: 2558955322048 [10, 20, 30] param: 2558955322048 [10, 20, 30, 100] copy param: 2558955322944 [10, 20, 30, 100] my_lst: 2558955322048 [10, 20, 30, 100]
4. global 和 nonlocal
global
和 nonlocal
关键字都用于在 Python 中处理变量作用域的问题。
4.1 global 关键字
在 Python 中,global
关键字用于在函数内部声明一个变量是全局变量,而不是局部变量。这意味着在函数内部对这个变量的操作将影响到全局作用域中的同名变量。
num = 100 lst = [10, 20, 30] print('num:', id(num), 'lst:', id(lst)) def test(): global num, lst num = 200 lst = [100, 200, 300] if __name__ == '__main__': test() print('num:', id(num), 'lst:', id(lst))
程序输出结果:
num: 140717977248512 lst: 1941628885376 num: 140717977251712 lst: 1941628884416
4.2 nonlocal 关键字
在 Python 中,nonlocal
关键字用于在一个嵌套的函数中,声明一个变量是来自于外部嵌套函数的局部变量,而不是最外层全局变量或者在当前函数中新建的局部变量。它允许在内部函数中修改封闭作用域(Enclosing Scope)中的变量的引用。
def test(): num = 100 lst = [10, 20, 30] print('num:', id(num), 'lst:', id(lst)) def func(): nonlocal num, lst num = 200 lst = [100, 200, 300] func() print('num:', id(num), 'lst:', id(lst)) if __name__ == '__main__': test()
程序输出结果:
num: 140717977248512 lst: 2112394140096 num: 140717977251712 lst: 2112394139136
5. 哈希性
- 不可变类型的对象是可哈希的(hashable)
- 可变类型的对象通常是不可哈希的
如果可哈希的话,那么该元素可以作为字典的键、或者集合元素。反之,则不可以。
def test(): # 报错: TypeError: unhashable type: 'list' # a = {10, 'abc', False, [10, 20, 30]} # print(a) # 报错: TypeError: unhashable type: 'list' b = {10: 10, 'abc': 20, (1, 2, 3): 30, [10, 20, 30]: 40} print(b) if __name__ == '__main__': test()