目录
1 闭包
问:
当调用完函数后,函数内定义的变量就销毁了,但有时需要保存函数内的这个变量,并在这个变量的基础上完成一系列的操作
比如: 每次在这个变量的基础上和其它数字进行求和计算,那怎么办呢?
注:闭包可以保存函数内的变量,而不会随着调用完函数而被销毁。
闭包的构成条件:有嵌套、有引用、有返回
'''
闭包程序三步走:① 有嵌套 ② 有引用 ③ 有返回
'''
def func():
num = 20 # 局部变量
def inner():
print(num)
return inner # 实际上inner函数并没有执行,只是返回了inner函数在内存中的地址
f = func() # 相当于把inner在内存中的地址0x7fbc9b3f8e18赋值给变量f
f() # 找到inner函数的内存地址,并执行器内部的代码(num=20),在于闭包函数保留了num=20这个局部变量
闭包的作用:正常情况下,当执行func()的时候,函数内部的变量num = 20,会随着函数的func函数的结束而被垃圾回收机制所回收。所以闭包的真正作用:就是可以在全局作用域中,实现间接对局部变量进行访问。
注意点:由于闭包引用了外部函数的变量,则外部函数的变量没有及时释放,消耗内存。
推荐:nonlocal关键字(在函数内部修改函数外部的变量,这个变量非全局变量)
2 装饰器
2.1 什么是装饰器
在不改变现有函数源代码以及函数调用方式的前提下,实现给函数增加额外的功能。
装饰器的本质就是一个闭包函数(三步:① 有嵌套 ② 有引用 ③ 有返回)
2.2 装饰器的定义
方式1: 传统方式: 变量名 = 装饰器名(原有函数名)
变量名()
方式2: 语法糖: @装饰器名 (这种方式最常见也最常用)
例方式2,发表评论前,都是需要先登录的。
先定义有发表评论的功能函数,然后在不改变原有函数的基础上,需要提示用户要先登录
'''
装饰器:本质就是一个闭包 ① 有嵌套 ② 有引用 ③ 有返回
'''
def check(fn):
def inner():
# 开发登录验证功能
print('验证登录')
# 执行原有函数
fn()
return inner
@check
def comment():
print('发表评论')
comment()
装饰器的构成条件:
①有嵌套:在函数嵌套(函数里面再定义函数)的前提下;
②有引用:内部函数使用了外部函数的变量(还包括外部函数的参数);
③有返回:外部函数返回了内部函数名;
④有额外功能:给需要装饰的原有函数增加额外功能。
装饰器的作用:
不改变原有函数的基础上,给原有函数增加额外功能。
装饰器本质上就是一个闭包函数。
2.3 四种类型函数的装饰器
1.无参无返回值:
以下例子同时使用了方法1和方法2.所以相当于装饰了两次,所以打印两次。
# 装饰器: 在不改变原有函数(定义和调用)基础上,额外增加新的功能
# 3.需求: 在原有函数计算出结果之前,先提示用户:正在计算中...
# 有嵌套
def outter(func):
def inner():
print("正在计算中...")
func()
return inner
# 1.定义原有函数
@outter # 4.语法糖方式装饰器开始装饰原有函数get_sum
def get_sum():
a = 1
b = 2
print(a + b)
# 4.1原始方式装饰器开始装饰原有函数get_sum
get_sum = outter(get_sum)
# 2.再调用原有函数
get_sum()
2.有参无返回值:
# 3.需求: 再原有函数出结果之前,输出: 正在计算中...
# 有嵌套
def oueter(func):
def inner(a, b):
# 有额外功能
print("正在计算中...")
# 有引用 TODO 此处func就是原有函数
func(a, b)
# 有返回
return inner
# 4.语法糖方式装饰原有函数
@oueter
# 1.定义原有函数
def func2(a, b):
sum = a + b
print(sum)
# 2.调用原有函数
# TODO 此时func2就是inner内部函数
func2(3, 4)
3.无参有返回值:
# 3.需求: 再原有函数出结果之前,输出: 正在计算中...
# 有嵌套
def oueter(func):
def inner():
# 有额外功能
print("正在计算中...")
# 有引用 TODO 此处func就是原有函数
return func()
# 有返回
return inner
# 4.语法糖方式装饰原有函数
@oueter
# 1.定义原有函数
def func2():
sum = 1 + 2
return sum
# 2.调用原有函数
# TODO 此时func2就是inner内部函数
print(func2())
#正在计算中...
#3
4.有参有返回值:
# 3.需求: 再原有函数出结果之前,输出: 正在计算中...
# 有嵌套
def oueter(func):
def inner(a, b):
# 有额外功能
print("正在计算中...")
# 有引用 TODO 此处func就是原有函数
return func(a ,b)
# 有返回
return inner
# 4.语法糖方式装饰原有函数
@oueter
# 1.定义 TODO 原有函数
def func2(a, b):
sum = a + b
return sum
# 2.调用原有函数
# TODO 此时func2就是inner内部函数
print(func2(10, 20))
#正在计算中...
#30
2.4 通用装饰器
# 3.需求: 在原有函数计算出结果之前,先提示用户:正在计算中...
# 有嵌套
def outter(func):
def inner(*args, **kwargs):
print("正在计算中...")
# 有引用 TODO: 此处func就是原有函数
data = func(*args, **kwargs)
return data
return inner
# 4.语法糖方式添加装饰器
@outter
# 1.先定义函数
# 功能是计算多个数的和,要求位置传参和关键字传参均可
def get_sum(*args, **kwargs):
sum = 0
for i in args:
sum += i
for i in kwargs.values():
sum += i
return sum
# 2.再调用函数
# TODO 此时get_sum就是inner内部函数
he = get_sum(1, 2, 3, 4, 5, a=6, b=7)
print(he) #28
离函数最近的装饰器先装饰,然后外面的装饰器再进行装饰,
由内到外的装饰过程
3 深浅拷贝
几个概念:
- 变量:是一个系统表的元素,拥有指向对象的连接空间
- 对象:被分配的一块内存,存储其所代表的值
- 引用:是自动形成的从变量到对象的指针
- 类型:属于对象,而非变量
- 不可变对象:一旦创建就不可修改的对象,包括数值类型、字符串、布尔类型、元组
*(该对象所指向的内存中的值不能被改变。当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。)*
- 可变对象:可以修改的对象,包括列表、字典、集合
*(该对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的地址,通俗点说就是原地改变。)*
当我们写到a = "python":
Python解释器干的事情:
① 创建变量a
② 创建一个对象(分配一块内存),来存储值 'python'
③ 将变量与对象,通过指针连接起来,从变量到对象的连接称之为引用(变量引用对象)
3.1 浅拷贝:
浅拷贝: 创建新对象,其内容是原对象的引用
浅拷贝之所以称为浅拷贝,是它仅仅只拷贝了一层,拷贝了最外围的对象本身,内部的元素都只是拷贝了一个引用而已
可变类型浅拷贝:
不可变类型浅拷贝:
3.2 深拷贝:
深拷贝:和浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。深拷贝出来的对象是一个全新的对象,不再与原来的对象有任何关联。所以改变原有被复制对象不会对已经复制出来的新对象产生影响。只有一种形式,copy模块中的deepcopy函数。
可变类型深拷贝:
不可变类型深拷贝:
总结:
浅copy跟深copy的区别:
对于不可变类型,浅copy跟深copy都copy不成功,相当于引用,新的对象与原对象共享内存中的子对象。
对于可变类型,对于第一层数据浅copy跟深copy都可以copy成功,从内存重新开辟一段空间给新的对象。但是对于第二层数据,比如列表里边套列表,列表里边套集合,列表里边套字典等,浅copy不成功,深copy会把第二层数据也会开辟一段空间。以下举例:
# 从copy模块导入浅拷贝功能
from copy import copy
# 从copy模块导入深拷贝功能
from copy import deepcopy
# 可变类型: 列表 列表嵌套列表或者集合或者字典
a = [1, 2, 3, [4, 5, [True, False]]]
b = copy(a)
c = deepcopy(a)
# 1层情况下: 深浅拷贝都成功
print('a:', a, 'b:', b, 'c:', c)
print('a:', id(a), 'b:', id(b), 'c:', id(c))
# 2层及以上: 浅拷贝只是引用传递,深拷贝是完全拷贝,深拷贝都开辟了新的空间地址,以后相互不受影响
print(a[3], b[3], c[3])
print(id(a[3]), id(b[3]), id(c[3]))
print(a[3][2], b[3][2], c[3][2])
print(id(a[3][2]), id(b[3][2]), id(c[3][2]))
结果:
deepcopy的深度要比copy更高。