变量、引用、对象
a = 0
上述语句中,创建了一个对象,这个对象是一个值,该值为0;再创建了一个变量a,变量a引用了对象。也可以理解成a是变量,0是对象,a引用了0。
变量是没有类型的,它可以指向任何类型的对象,在使用变量前必须先赋值,也就是说变量在被赋值时才创建的。可以看作是对象的一个临时标签。
对象是有类型的,其本质是分配的一块内存,存放着值。
对象有三要素:唯一标识(Id)、类型标识(Type)、值(Value)。
对象的两个标准的头部信息:类型标识符、引用计数器。
- Id 用于唯一标识对象,也就是计算机内存中的地址,id(obj)返回对象唯一标识
- Type 表示对象存储的数据的类型,type(obj)返回对象所属类型
- Value 表示对象存储的数据的信息,print(obj)打印值
- 引用计数器 用于记录当前对象的引用的数目,当它的值为0时,对象就会被回收
引用是一种关系,以内存中的指针的形式实现。
代码例子:
a = 0
print(id(a))
print(type(a))
print(a)
运行结果:
2157821427920
<class ‘int’>
0
赋值
赋值实际上可以理解为给对象取别名。赋值有两种情况:可变对象(列表、字典、集合)赋值 和 不可变对象(数值、字符串、元组)赋值。
可变对象赋值
代码
a = [0,1,2,3,4]
b = a
print(f"修改前,a和b的值:a = {a},b = {b}")
print(f"修改前,a和b的地址:a = {id(a)},b = {id(b)}")
b[0] = 5
print(f"修改 b[0] 后,a和b的值:a = {a},b = {b}")
print(f"修改 b[0] 后,a和b的地址:a = {id(a)},b = {id(b)}")
a[0] = 6
print(f"修改 a[0] 后,a和b的值:a = {a},b = {b}")
print(f"修改 a[0] 后,a和b的地址:a = {id(a)},b = {id(b)}")
运行结果
修改前,a和b的值:a = [0, 1, 2, 3, 4],b = [0, 1, 2, 3, 4]
修改前,a和b的地址:a = 2769219675904,b = 2769219675904
修改 b[0] 后,a和b的值:a = [5, 1, 2, 3, 4],b = [5, 1, 2, 3, 4]
修改 b[0] 后,a和b的地址:a = 2769219675904,b = 2769219675904
修改 a[0] 后,a和b的值:a = [6, 1, 2, 3, 4],b = [6, 1, 2, 3, 4]
修改 a[0] 后,a和b的地址:a = 2769219675904,b = 2769219675904
结论
从上述结果中看出,列表a赋值给列表b后,a和b指向同一个对象,即同一个内存空间,改变a、b其中一个的值,另一方也会跟着改变;同时值修改前后,a和b始终指向同一个内存。
不可变对象赋值
代码
a = 0
b = a
print(f"修改前,a和b的值:a = {a},b = {b}")
print(f"修改前,a和b的地址:a = {id(a)},b = {id(b)}")
b = 5
print(f"修改b后,a和b的值:a = {a},b = {b}")
print(f"修改b后,a和b的地址:a = {id(a)},b = {id(b)}")
a = 6
print(f"修改a后,a和b的值:a = {a},b = {b}")
print(f"修改a后,a和b的地址:a = {id(a)},b = {id(b)}")
运行结果
修改前,a和b的值:a = 0,b = 0
修改前,a和b的地址:a = 2913954955472,b = 2913954955472
修改b后,a和b的值:a = 0,b = 5
修改b后,a和b的地址:a = 2913954955472,b = 2913954955632
修改a后,a和b的值:a = 6,b = 5
修改a后,a和b的地址:a = 2913954955664,b = 2913954955632
结论
从上述结果中看出,a赋值给b后,a和b指向同一个对象,即同一个内存空间,修改b值,a值不会改变,同时b的地址会改变;同理,修改a值,b值不会改变,同时a的地址也会发生变化。
拷贝
拷贝是将旧变量引用的对象复制,新变量引用复制出来的新对象,也就是说拷贝可以创建出一个新的对象,新对象的值和旧对象是一致的。拷贝分为浅拷贝和深拷贝。
浅拷贝
浅拷贝有字典的copy()方法和分片表达式L[:],也有copy模块中copy.copy(a)方法。
先看例子第一个例子:
lst = [0, 1, 2, 3, 4]
lst_copy = lst.copy()
dic = {'a':1,'b':2}
dic_copy = dic.copy()
print("修改前a、b、c的地址值和值:")
print(f"lst的地址:{id(lst)},a = {lst}")
print(f"lst_copy的地址:{id(lst_copy)},a = {lst_copy}")
print(f"dic的地址:{id(dic)},a = {dic}")
print(f"dic_copy的地址:{id(dic_copy)},a = {dic_copy}")
print('=======================================')
lst[0] = 11
lst_copy[1] = 22
dic['c'] = 3
dic_copy['d'] = 4
print("修改后a、b、c的地址值和值:")
print(f"lst的地址:{id(lst)},a = {lst}")
print(f"lst_copy的地址:{id(lst_copy)},a = {lst_copy}")
print(f"dic的地址:{id(dic)},a = {dic}")
print(f"dic_copy的地址:{id(dic_copy)},a = {dic_copy}")
运行结果:
修改前a、b、c的地址值和值:
lst的地址:1818901275968,a = [0, 1, 2, 3, 4]
lst_copy的地址:1818901282304,a = [0, 1, 2, 3, 4]
dic的地址:1818625374080,a = {‘a’: 1, ‘b’: 2}
dic_copy的地址:1818625374336,a = {‘a’: 1, ‘b’: 2}
=======================================
修改后a、b、c的地址值和值:
lst的地址:1818901275968,a = [11, 1, 2, 3, 4]
lst_copy的地址:1818901282304,a = [0, 22, 2, 3, 4]
dic的地址:1818625374080,a = {‘a’: 1, ‘b’: 2, ‘c’: 3}
dic_copy的地址:1818625374336,a = {‘a’: 1, ‘b’: 2, ‘d’: 4}
从上面的运行结果看,浅拷贝后是分配一个新的内存,把旧对象的内容复制到新分配的内存中,即生成一个新对象,两个变量修改不会相互影响。
下面再看第二个例子:
import copy
dic = {'lst':[0,1,2,3,4],'int':0}
dic_copy = copy.copy(dic)
print('修改前:')
print(f'dic的值为{dic},地址为{id(dic)}')
print(f'dic_copy的值为{dic_copy},地址为{id(dic_copy)}')
print('=================================')
dic['str'] = 'abcde'
dic_copy['float'] = 2.5
print('往字典添加元素后:')
print(f'dic的值为{dic},地址为{id(dic)}')
print(f'dic_copy的值为{dic_copy},地址为{id(dic_copy)}')
print('=================================')
dic['lst'].append(555)
dic_copy['lst'].append(666)
print('分别修改键为“lst”的值后:')
print(f'dic的值为{dic},地址为{id(dic)}')
print(f'dic_copy的值为{dic_copy},地址为{id(dic_copy)}')
print('=================================')
dic['int'] = 1
dic_copy['int'] = 2
print('分别修改键为“int”的值后:')
print(f'dic的值为{dic},地址为{id(dic)}')
print(f'dic_copy的值为{dic_copy},地址为{id(dic_copy)}')
print('=================================')
print("dic['lst']和dic_copy['lst']的地址分别为:")
print(f"{id(dic['lst'])}, {id(dic_copy['lst'])}")
运行结果:
修改前:
dic的值为{‘lst’: [0, 1, 2, 3, 4], ‘int’: 0},地址为2246132692864
dic_copy的值为{‘lst’: [0, 1, 2, 3, 4], ‘int’: 0},地址为2246132693120
=================================
往字典添加元素后:
dic的值为{‘lst’: [0, 1, 2, 3, 4], ‘int’: 0, ‘str’: ‘abcde’},地址为2246132692864
dic_copy的值为{‘lst’: [0, 1, 2, 3, 4], ‘int’: 0, ‘float’: 2.5},地址为2246132693120
=================================
分别修改键为“lst”的值后:
dic的值为{‘lst’: [0, 1, 2, 3, 4, 555, 666], ‘int’: 0, ‘str’: ‘abcde’},地址为2246132692864
dic_copy的值为{‘lst’: [0, 1, 2, 3, 4, 555, 666], ‘int’: 0, ‘float’: 2.5},地址为2246132693120
=================================
分别修改键为“int”的值后:
dic的值为{‘lst’: [0, 1, 2, 3, 4, 555, 666], ‘int’: 1, ‘str’: ‘abcde’},地址为2246132692864
dic_copy的值为{‘lst’: [0, 1, 2, 3, 4, 555, 666], ‘int’: 2, ‘float’: 2.5},地址为2246132693120
=================================
dic[‘lst’]和dic_copy[‘lst’]的地址分别为:
2244261620096, 2244261620096
从上述结果看出,经过浅复制后的字典,字典地址不一样,但是在修改字典里列表的值时,它们相互影响,字典里可变对象的地址是一样的。
综上两个例子可以看出,浅拷贝只能复制对象本身,如果对象中引用了其他对象,那么被引用的对象是不能被复制的,也就是说浅复制只能复制父对象,父对象中的子对象是不可以复制的。
深拷贝
copy模块中的深拷贝方法是copy.deepcopy(x)
对于一些简单的对象而言,深拷贝和浅拷贝是一样的,如果需要复制一个既有不可变对象,又有可变对象的复杂对象时,需要用深拷贝。深拷贝会完全复制原变量相关的所有数据,在内存中生成一套完全一样的内容,无论怎么修改都不会相互影响。
import copy
dic = {'lst':[0,1,2,3,4],'int':0}
dic_copy = copy.deepcopy(dic)
print('修改前:')
print(f'dic的值为{dic},地址为{id(dic)}')
print(f'dic_copy的值为{dic_copy},地址为{id(dic_copy)}')
print('=================================')
dic['str'] = 'abcde'
dic_copy['float'] = 2.5
print('往字典添加元素后:')
print(f'dic的值为{dic},地址为{id(dic)}')
print(f'dic_copy的值为{dic_copy},地址为{id(dic_copy)}')
print('=================================')
dic['lst'].append(555)
dic_copy['lst'].append(666)
print('分别修改键为“lst”的值后:')
print(f'dic的值为{dic},地址为{id(dic)}')
print(f'dic_copy的值为{dic_copy},地址为{id(dic_copy)}')
print('=================================')
dic['int'] = 1
dic_copy['int'] = 2
print('分别修改键为“int”的值后:')
print(f'dic的值为{dic},地址为{id(dic)}')
print(f'dic_copy的值为{dic_copy},地址为{id(dic_copy)}')
print('=================================')
print("dic['lst']和dic_copy['lst']的地址分别为:")
print(f"{id(dic['lst'])}, {id(dic_copy['lst'])}")
运行结果:
修改前:
dic的值为{‘lst’: [0, 1, 2, 3, 4], ‘int’: 0},地址为2171635693440
dic_copy的值为{‘lst’: [0, 1, 2, 3, 4], ‘int’: 0},地址为2171910224192
=================================
往字典添加元素后:
dic的值为{‘lst’: [0, 1, 2, 3, 4], ‘int’: 0, ‘str’: ‘abcde’},地址为2171635693440
dic_copy的值为{‘lst’: [0, 1, 2, 3, 4], ‘int’: 0, ‘float’: 2.5},地址为2171910224192
=================================
分别修改键为“lst”的值后:
dic的值为{‘lst’: [0, 1, 2, 3, 4, 555], ‘int’: 0, ‘str’: ‘abcde’},地址为2171635693440
dic_copy的值为{‘lst’: [0, 1, 2, 3, 4, 666], ‘int’: 0, ‘float’: 2.5},地址为2171910224192
=================================
分别修改键为“int”的值后:
dic的值为{‘lst’: [0, 1, 2, 3, 4, 555], ‘int’: 1, ‘str’: ‘abcde’},地址为2171635693440
dic_copy的值为{‘lst’: [0, 1, 2, 3, 4, 666], ‘int’: 2, ‘float’: 2.5},地址为2171910224192
=================================
dic[‘lst’]和dic_copy[‘lst’]的地址分别为:
2171911857984, 2171911857088
在运行结果可以看出,深拷贝是完全复制旧对象,复制父对象的同时,递归复制所有子对象,新对象和旧对象的内存地址不一样。
总结
Python的赋值,一般情况下,对于不可变对象而言,可以理解为拷贝;对于可变对象而言,就是引用对象。(注: 对静态变量首次传递时也是引用,当需要修改静态变量时,因为静态变量不能改变,所以需要生成一个新的空间存储数据)
Python的拷贝,分为浅拷贝和深拷贝。对于简单对象(即对象里只有不可变对象,没有引用其他可变对象),深浅拷贝都一样;对于对象里引用了其他对象的,既有可变对象,又有不可变对象的,浅拷贝只能拷贝父对象,不能拷贝子对象,深拷贝是可以将父子对象都拷贝。
如有错漏之处,请批评指正,谢谢!