Python的按对象传递CPL
C++中传递一般分两种:按值传递和按引用传递。简单来讲,函数的按值传递就是函数接收到某个传递来的值时,会创建一份该值的备份,然后函数的所有操作均在此备份的基础上进行,对原始值无影响,初始值不会发生更改;而按引用传递是直接在原始值的基础上进行操作,会使原始值发生更改。
而Python中,一切皆对象,自然而然Python是按照对象进行传递。这种方式相当于按值传递和按引用传递的一种结合。如果函数收到的是一个可变对象( list, dict, set )的引用,就能修改对象的原始值–相当于通过“引用传递”来赋值。如果函数收到的是一个不可变变量(strings, tuples, 和numbers)的引用,就不能直接修改原始对象–相当于通过“值传递”来赋值。
看两个例子:
#例1:
a = 1
def fun1(a):
a = 2
fun1(a)
print(a) # 输出结果为1
#例2:
a = []
def fun2(a):
a.append(1)
fun2(a)
print(a) # 输出结果为[1]
当一个引用传递给函数的时候,函数自动复制一份引用,这个函数里的引用和外边的引用就一点关系都没有了,所以第一个例子里函数把引用指向了一个不可变对象,当函数返回的时候,外面的引用没有发生改变.而第二个例子就不一样了,函数内的引用指向的是可变对象,对它的操作就和定位了指针地址一样,在内存里进行了修改.
通过id来看引用a的内存地址可以比较理解:
a = 1
def fun1(a):
print("func_in",id(a)) # func_in 41322472
a = 2
print("re-point",id(a), id(2)) # re-point 41322448 41322448
print("func_out",id(a), id(1)) # func_out 41322472 41322472
fun1(a)
print(a) # 1
注:具体的值在不同电脑上运行时可能不同。
可以看到,在执行完a = 2之后,a引用中保存的值,即内存地址发生变化,由原来1对象的所在的地址变成了2这个实体对象的内存地址。
而第2个例子a引用保存的内存值就不会发生变化:
a = []
def fun2(a):
print("func_in",id(a)) # func_in 53629256
a.append(1)
print("func_out",id(a)) # func_out 53629256
fun2(a)
print(a) # [1]
谈到Python的按对象传递,就自然而然引出了Python中的copy()和deepcopy()的区别问题。
copy()为浅拷贝
deepcopy()为深拷贝
浅拷贝是新创建了一个跟原对象一样的类型,但是其内容是对原对象元素的引用。这个拷贝的对象本身是新的,但内容不是。如果原对象的元素包含不是基本数据结构,而是list、dict或者对象的话,那么原对象或者拷贝对象改变list、dict或者对象里面的内容的话,会导致二者同时发生改变。
深拷贝则是对原对象的完全拷贝,包含对象里面的子对象的拷贝,因此拷贝对象和原对象二者是完全独立,任何一方的改变对另外一方都不会产生任何的影响。
import copy
a = [1, 2, 3, 4, ['a', 'b']] #原始对象
b = a #赋值,传对象的引用
c = copy.copy(a) #对象拷贝,浅拷贝
d = copy.deepcopy(a) #对象拷贝,深拷贝
a.append(5) #修改对象a
a[4].append('c') #修改对象a中的['a', 'b']数组对象
print('a = ', a)
print('b = ', b)
print('c = ', c)
print('d = ', d)
# 输出结果:
# a = [1, 2, 3, 4, ['a', 'b', 'c'], 5]
# b = [1, 2, 3, 4, ['a', 'b', 'c'], 5]
# c = [1, 2, 3, 4, ['a', 'b', 'c']]
# d = [1, 2, 3, 4, ['a', 'b']]