深拷贝和浅拷贝
[掌握]浅拷贝
==目标:==掌握什么是浅拷贝。
浅拷贝需要使用copy模块下的copy()函数。
函数名 | 含义 |
---|---|
copy(t) | 使用浅拷贝来拷贝信息。 |
浅拷贝只对可变类型的第一层对象进行拷贝,对拷贝的对象开辟新的内存空间进行存储,且不会拷贝对象内部的子对象。
例如,一起来完成:
(1)使用浅拷贝来拷贝不可变数据类型:数字19和元组(12, 13, 14, );
(2)使用浅拷贝来拷贝可变数据类型:列表[10, 20, 30]和列表[a, b];
(3)观察拷贝不可变类型、可变类型数据的效果。
#(1)使用浅拷贝来拷贝不可变数据类型:数字19和元组(12, 13, 14, );
import copy
print('-------------不可变类型:数字---------------')
a = 19
b = copy.copy(a)
print(a,b) # 值相同
print(id(a),id(b)) # 地址相同
print('-------------不可变类型:元组---------------')
tuple1 = (12, 13, 14)
tuple2 = copy.copy(tuple1)
print(tuple1,tuple2) # 值相同
print(id(tuple1),id(tuple2)) # 地址相同
#(2)使用浅拷贝来拷贝可变数据类型:列表[12, 13, 14]
print('-------------可变类型:列表---------------')
list1 = [12, 13, 14]
list2 = copy.copy(list1)
print(list1,list2) # 值相同
print(id(list1),id(list2)) # 地址不同
print('-------------可变类型:列表嵌套---------------')
list1 = [12, 13, 14,[77,88,99]]
list2 = copy.copy(list1)
print(list1,list2) # 值相同
print(id(list1),id(list2)) # 外层地址不同
print(id(list1[3]),id(list2[3])) # 内层地址竟然相同
list1[3][0] = 777 # list1把内层列表的值修改之后,list2的内层列表也跟着改,这样不合理
print(list1,list2)
# 结论
# 浅拷贝本质是只拷贝了内存地址值
# 对不可变类型的数据进行浅拷贝时, 内存地址值相同, 值也相同, 通过指向原有数据内容的内存地址值.
# 浅拷贝拷贝的是【内存地址值】
# 浅拷贝了可变数据类型的数据后, 外层内存地址会变化,值也会变化, 内层地址不会变化,值也不会变化
==总结:==
(1)当要了解浅拷贝时,需要使用copy模块的()函数;==A、copy()==;B、deepcopy();
(2)注意:对于浅拷贝的理解,尽量分为拷贝不可变类型和可变类型的数据来查看。
(3)浅拷贝适用于对象内部主要是基本类型(如int, float, str等)或不需要深拷贝的情况。
[掌握]深拷贝
==目标:==掌握深拷贝的使用。
深拷贝需要使用copy模块下的deepcopy()函数:
函数名 | 含义 |
---|---|
deepcopy(t) | 使用深拷贝来拷贝信息。 |
深拷贝指的是拷贝一个对象时,只要发现对象有可变类型就会对该对象到最后一个可变类型的每一层对象就行拷贝,对每一层拷贝的对象都会开辟新的内存空间进行存储。
通俗地说,深拷贝就是对一个对象中所有层次的拷贝,即既拷贝了引用,也拷贝了内容。
例如,一起来完成:
(1)使用深拷贝来拷贝不可变数据类型:字符串"hello"和元组(100, 200, 300, );
(2)使用深拷贝来拷贝可变数据类型:列表[11, 22, 33]和列表[a, b];
(3)观察拷贝不可变类型、可变类型数据的效果。
#(1)使用浅拷贝来拷贝不可变数据类型:数字19和元组(12, 13, 14, );
import copy
print('-------------不可变类型:数字---------------')
a = 19
b = copy.deepcopy(a)
print(a,b) # 值相同
print(id(a),id(b)) # 地址相同
print('-------------不可变类型:元组---------------')
tuple1 = (12, 13, 14)
tuple2 = copy.deepcopy(tuple1)
print(tuple1,tuple2) # 值相同
print(id(tuple1),id(tuple2)) # 地址相同
#(2)使用浅拷贝来拷贝可变数据类型:列表[12, 13, 14]
print('-------------可变类型:列表---------------')
list1 = [12, 13, 14]
list2 = copy.deepcopy(list1)
print(list1,list2) # 值相同
print(id(list1),id(list2)) # 地址不同
print('-------------可变类型:列表嵌套---------------')
list1 = [12, 13, 14,[77,88,99]]
list2 = copy.deepcopy(list1)
print(list1,list2) # 值相同
print(id(list1),id(list2)) # 外层地址不同
print(id(list1[3]),id(list2[3])) # 内层地址竟然相同
list1[3][0] = 777 # list1把内层列表的值修改之后,list2的内层列表也跟着改,这样不合理
print(list1,list2)
print('-------------可变类型:列表嵌套,直接赋值---------------')
list1 = [12, 13, 14,[77,88,99]]
list2 = list1
print(list1,list2) # 如果直接赋值,值相同
print(id(list1),id(list2)) # 外层地址相同
print(id(list1[3]),id(list2[3])) # 内层地址也相同
list1[3][0] = 777 # list1把内层列表的值修改之后,list2的内层列表也跟着改,这样不合理
print(list1,list2)
# 结论
# 深拷贝既拷贝了内存地址值,又拷贝了内容
# 对不可变类型的数据进行深拷贝时, 内存地址值相同, 值也相同。
# 深拷贝拷贝了引用和内容
# 深拷贝了可变数据类型的数据后, 内存地址值会变化, 值是相同的. [从表面上看: 深浅拷贝效果一致]
==总结:==
(1)当要了解深拷贝时,需要使用copy模块的()函数;A、copy();==B、deepcopy()==;
(2)注意:对深拷贝、浅拷贝的理解,可以通过深浅了解拷贝得多一些。
[掌握]浅拷贝和深拷贝的区别
==目标:==了解浅拷贝和深拷贝的区别?
对于浅拷贝和深拷贝,区别如下:
函数名 | 含义 |
---|---|
deepcopy(t) | 完全拷贝了父对象及其子对象。 |
copy(t) | 拷贝父对象,不会拷贝对象的内部的子对象。 |
例如,一起来完成:
(1)分别使用浅拷贝和深拷贝来拷贝可变类型:列表[m, n];
(2)观察父对象和子对象引用和数值的变化效果。
#(1)使用浅拷贝来拷贝不可变数据类型:数字19和元组(12, 13, 14, );
import copy
print('-------------不可变类型:数字---------------')
a = 19
b = copy.deepcopy(a)
print(a,b) # 值相同
print(id(a),id(b)) # 地址相同
print('-------------不可变类型:元组---------------')
tuple1 = (12, 13, 14)
tuple2 = copy.deepcopy(tuple1)
print(tuple1,tuple2) # 值相同
print(id(tuple1),id(tuple2)) # 地址相同
#(2)使用浅拷贝来拷贝可变数据类型:列表[12, 13, 14]
print('-------------可变类型:列表---------------')
list1 = [12, 13, 14]
list2 = copy.deepcopy(list1)
print(list1,list2) # 值相同
print(id(list1),id(list2)) # 地址不同
print('-------------可变类型:列表嵌套---------------')
list1 = [12, 13, 14,[77,88,99]]
list2 = copy.deepcopy(list1)
print(list1,list2) # 值相同
print(id(list1),id(list2)) # 外层地址不同
print(id(list1[3]),id(list2[3])) # 内层地址竟然相同
list1[3][0] = 777 # list1把内层列表的值修改之后,list2的内层列表也跟着改,这样不合理
print(list1,list2)
# 结论
# 通俗: 浅拷贝 -> 给文件夹制作了一个快捷方式; 深拷贝 ->复制+粘贴了一份新的, 里面的所有内容都是全新
==总结:==
(1)当拷贝多层数据时,才能发现深拷贝、浅拷贝的区别在于是否能完全拷贝子对象;
(2)请问:深拷贝是完全拷贝,既拷贝了父对象,也拷贝了子对象,这句话正确吗?==A、正确;B、错误==。
(3)深拷贝适用于对象内部包含较多非基本类型对象,并且需要确保对象之间完全独立的情况。
深浅拷贝的区别总结
浅拷贝只复制了原始对象的引用,而深拷贝则是递归地复制原始对象及嵌套对象,从而得到完全独立的新对象副本。
函数知识
[掌握]函数的定义与调用
==目标:==掌握函数的快速使用。
在之前的学习中,已经学习过函数。一起来看看简单和综合函数语法格式。
最简单的语法:
# 定义
def 函数名():
代码
...
# 调用
函数名()
综合的语法:
# 定义
def 函数名([参数1, 参数2, 参数3, ...]):
代码
...
[return 值]
# 调用
函数名([值1, 值2, 值3, ...])
接着,再来总结下函数的使用特点:
(1)先定义,后调用;
(2)不调用,不执行;
(3)调用一次,执行一次;
(4)当输出有返回值的函数时,输出的是具体的返回值结果;当输出没有返回值的函数时,输出的是None。
例如,一起来完成:
(1)定义并调用一个有返回值的函数func();
(2)定义并调用一个无返回值的函数test()。
# 1.定义有返回值的func()
# def func():
# print("Hello World..")
# return 100
#
# # 2.调用
# # func()
# print(func())
# 3.定义无返回值的test()
def test():
print("人生苦短,我用Python")
# 4.调用
print(test())
# 人生苦短,我用Python
# None
==总结:==
(1)当调用函数时,要给在调用函数名后添加()括号;
(2)注意:当输出没有返回值的函数调用时,输出的是()。==A、None==;B、0;
[掌握]函数名记录的是引用
==目标:==掌握函数名记录的是引用。
我们已经知道,当要调用一个函数时,需要:
函数名([值1, 值2, 值3, ...])
说明:要调用函数,记得添加()括号。
那么,如果直接输出函数名,会是什么效果呢?
例如,一起来完成:
(1)定义一个有返回值的函数show();
(2)接着,直接输出函数名,观察输出结果;
(3)结论:函数名记录的是函数引用,即内存地址值。
def show():
print('我是一个小小的函数')
print(show) #<function show at 0x000001BF04195168> 函数名就是一个地址引用
# show就是一个地址,指向函数的定义的那片内存空间,把函数的地址赋值为show2,show2也指向了那片内存空间
# show2也称为函数的引用地址
show2 = show
#使用show2来调用函数
show2()
==总结:==
(1)当定义了函数后,就相当于给函数名在内存中开辟了一个新的内存空间;
(2)注意:直接输出函数名时,输出的是函数对应的()。==A、内存地址值==;B、None;
[了解]函数名当作参数传递
==目标:==了解把函数名当作参数进行传递。
我们已经知道,函数名记录的是函数的引用,即内存地址值。
那么,当把函数名直接作为参数进行传递时,从本质上说,传递的是:对应函数的内存地址值。
def 函数名A():
代码
...def function(num):
代码
num() # 调用函数
...
function(函数名A) # 把函数名当作参数传递
例如,一起来完成:
(1)定义一个无参函数test();
(2)定义有一个参数的函数func();
(3)把无参函数test()的函数名传递给有参函数func(),并观察效果。
# (1)定义一个无参函数test();
# (2)定义有一个参数的函数func();
# (3)把无参函数test()的函数名传递给有参函数func(),并观察效果。
def test():
print('这个是test函数')
def func(f_name):
print('func函数开始')
f_name()
print('func函数结束')
def add(x,y):
return x + y
def func2(f_name): # f_name = lambda x,y:x+y
print('func2函数开始')
a = 10
b = 20
print(f'两个数的和是:{f_name(a,b)}')
print('func2函数结束')
if __name__ == '__main__':
func(test)
func2(lambda x,y:x+y)
func2(add) # 作用同上
==总结:==
(1)当把函数名直接传递时,若要查看调用函数的效果,需要在函数内给参数添加()括号进行调用;
(2)把函数名当作参数进行传递,可以应用于闭包和装饰器中。
闭包
[掌握]闭包的作用
==目标:==掌握闭包的作用。
在之前的学习中,我们会发现:当调用完函数后,函数内定义的变量就销毁了。
如果要保存函数内的变量,就可以使用闭包。
先来看看闭包的作用:闭包可以保存函数内的变量,而不会随着调用完函数而被销毁。
例如,一起来完成:
(1)此处来了解闭包的作用:保存函数内的变量;
(2)定义一个有返回值的函数;
(3)然后,调用函数并使用变量存储返回值结果;
(4)在变量基础上,进行重复累加数值,观察效果。
# 1.定义有返回值的函数
def func():
a = 10
print("我是一名程序员!!")
return a
# 2.使用变量来接收函数调用
# func()
number = func() # a的值存储到了number -类似闭包的语法
# 3.观察: 函数虽然调用结束了,但是变量被保存起来 = 闭包
print(number+23)
print(number+44)
==总结:==
(1)请问:闭包可以用来保存函数内的变量值,而变量不会随着调用函数结束而销毁,这句话正确吗?==A、正确==;B、错误。
[掌握]使用闭包
==目标:==掌握闭包的使用。
闭包指的是:在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数。
此时,把这个使用外部函数变量的内部函数,就称为闭包。
定义闭包语法:
# 外部函数
def 外部函数名(外部参数):
# 内部函数
def 内部函数名(内部参数):
...[使用外部函数的变量]
代码
return 内部函数名 # 闭包
调用闭包语法:
变量名 = 外部函数名([外部参数列表]) 变量名([内部参数列表])
要构成闭包,需要满足3个条件:
(1)有嵌套:外部函数中嵌套了一个内部函数 (2)有引用:内部函数中使用了外部函数的变量或参数 (3)有返回:外部函数返回内部函数的函数名
说明:
(1)为了更好的理解闭包,建议:外部函数命名为outer、内部函数命名为inner;
(2)当熟练使用闭包后,再任意命名即可。
例如,一起来完成:
(1)定义一个用于求和的闭包;
(2)其中,外部函数有参数x,内部函数有参数y;;
(3)然后调用并求解两数之和及输出,观察效果。
# 定义外部函数
def outer(x):
def inner(y): # 函数定义嵌套
result = x + y # 内部函数使用外部函数变量
print(f'两个数的和是:{result}')
return inner # 外部函数返回内部函数的名字
if __name__ == '__main__':
inner_func = outer(100) # 调用外部函数,返回的值就是内部函数的名字,,就是返回一个函数
inner_func(10) # 调用内部函数,外部函数的x是100
inner_func(20) # 调用内部函数,外部函数的x是100
==总结:==
(1)构成闭包的3个条件:()、有引用、有返回;==A、有嵌套;==B、有额外功能;
(2)注意:闭包可以用于处理装饰器的使用。
[了解]nonlocal关键字
==目标:==了解nonlocal关键字的简单使用。
在闭包的使用过程中,当要在内部函数中修改外部函数的变量,需要使用nonlocal提前声明。
nonlocal关键字语法:
nonlocal 变量名
说明:
当变量报错时,记得使用nonlocal声明。
例如,一起来完成:
(1)编写一个闭包,并让内部函数去修改外部函数内的变量a = 100;
(2)记得使用【nonlocal 变量名】提前声明,并观察效果。
# 当你希望在内部函数中修改外边函数的变量,就应该使用nolocal关键字来声明
# 定义外部函数
def outer():
a = 10 # 外部函数的局部变量
def inner(): # 函数定义嵌套
nonlocal a # 声明这里的a就是外部函数的变量
a += 1 # 内部函数修改该变量
print(f'a的值是:{a}')
return inner # 外部函数返回内部函数的名字
if __name__ == '__main__':
inner_func = outer()
inner_func()
==总结:==
(1)若要声明能够让内部函数去修改外部函数的变量,则要使用()关键字;==A、nonlocal==;B、global;
(2)注意:当要在函数中修改全局变量的值时,要记得使用global给全局变量提前声明。