python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > python闭包装饰器和深浅拷贝

Python中的闭包装饰器和深浅拷贝案例演示

作者:大河之键天上来

本文给大家介绍Python中的闭包装饰器和深浅拷贝,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

1.闭包

说起闭包之前,我们需要了解为什么会有闭包?

问题1:为什么在全局作用域中无法访问局部变量

答:主要原因在于,在Python的底层存在一个“垃圾回收机制”,主要的作用就是回收内存空间。加快计算机的运行。我们在Python代码中定义的变量也是需要占用内存的,所以Python为了回收已经被已经过的内存,会自动将函数运行以后的内部变量和程序直接回收。

问题2:有没有办法把函数内部的局部变量保留

答:使用闭包,闭包可以保存函数内的变量,而不会随着调用完函数而被销毁

在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包。

闭包三大条件:

1.有嵌套:外部函数内嵌内部函数

2.有引用:内部函数引用外部函数变量

3.有返回值:外部函数返回内部函数名(return)

注意点:

由于闭包引用了外部函数的变量,则外部函数的变量没有及时释放,消耗内存。

"""
闭包:
    概述:
        使用了外部函数变量的内部函数,称之为闭包。
    回顾:局部变量的生命周期?
        局部变量随着函数的调用而存在,随着函数的调用完毕而销毁,即:函数执行结束后,变量就被释放了。
    格式:
        def 外部函数名(形参列表):
            可以在这里定义 外部函数的 局部变量
            def 内部函数名(形参列表):
                在这里使用 外部函数的变量
            return 内部函数名
    闭包的三个前提条件:
        1:有嵌套,即:有外部函数,有内部函数。
        2:有引用,即:在内部函数中,有使用外部函数的引用。
        3:有返回,即:在外部函数中,返回内部函数(对象)
"""
# 需求1:回顾局部变量的作用域。
def method():
    number = 10
    return number
# 需求2:演示闭包,定义用于求和的闭包函数,外部函数有参数num1,内部函数有参数num2,内部函数返回num1+num2
def outer(num1):                        # 定义了外部函数outer
    def inner(num2):                    # 定义了内部函数inner
        sum = num1+num2                 # 使用了外部函数的变量num1
        print(f"求和的结果是:{sum}")
    return inner                        # 外部函数返回内部函数对象
if __name__ == '__main__':
    # 目前:函数每次调用都会生成一个新的number变量,函数调用结束后,number变量就会销毁。
    print(method())  # 10
    print(method()+1) # 11
    print(method()+1) # 11
    print(method()+1) # 11
    print(20* "-")
    """
        fn = outer(10) # fn是外部函数outer的返回值,即内部函数inner
        fn = def inner(num2):                       # 定义了内部函数inner
                sum = 10 + num2                     # 使用了外部函数的变量num1
                print(f"求和的结果是:{sum}")
    """
    fn = outer(10) # fn是外部函数outer的返回值,即内部函数inner
    """
    此时  fn = outer(10)
     = def outer(10)
        def inner(num2):                    
            sum = 10 + num2                 
            print(f"求和的结果是:{sum}")
    """
    """
        fn(1)
        inner(1):                            # 定义了内部函数inner
                sum = 10 + 1                 # 使用了外部函数的变量num1
                print(f"求和的结果是:{sum}")
    """
    fn(1)
    """
        fn(2)
        inner(2):                            # 定义了内部函数inner
                sum = 10 + 2                 # 使用了外部函数的变量num1
                print(f"求和的结果是:{sum}")
    """
    fn(2)

1.1nonlocal

global: 声明全局变量
nonlocal: 声明能够让内部函数去修改外部函数的变量

"""
nonlocal关键字的介绍:
    概述:
        是一个关键字,可以实现让内部函数去修改外部函数的变量值。
    回顾:
        global      声明变量    ->为全局变量
        nonlocal    声明变量    ->可以被内部函数修改
"""
# 需求:编写一个闭包,让内部函数去访问外部函数的变量,并修改她的值,观察结果
# 1:定义闭包代码,让内部函数去修改外部函数的变量值
def fn_outer(): # 声明外部函数
    x = 100
    def fn_inner(): # 声明内部函数
        # 核心,内部函数想修改外部函数的变量值,该变量值需要用到nonlocal关键字修饰
        nonlocal x
        x = x + 20  # 在内部函数中,去修改外部函数的变量值
        print(f"x的值为:{x}")
    return fn_inner
if __name__ == '__main__':
    fn = fn_outer()
    fn()
    fn()
    fn()

2.装饰器

概述:装饰器的作用就是在不改变原有函数的基础上,给函数增加额外功能,装饰器本质上就是一个闭包函数

装饰器构成的四个条件:

①有嵌套:在函数嵌套(函数里面再定义函数)的前提下;
②有引用:内部函数使用了外部函数的变量(还包括外部函数的参数);
③有返回:外部函数返回了内部函数名;
④有额外功能:给需要装饰的原有函数增加额外功能。

装饰器语法:

方式1: 传统方式:  变量名 = 装饰器名(原有函数名)
            变量名()

方式2: 语法糖: @装饰器名   (这种方式最常见也最常用)   

"""
装饰器的介绍:
    概述:
        装饰器=闭包函数,即:装饰器是闭包函数的一种写法。
    目的/作用:
        在不改变原有函数的基础上,对原有函数的功能做加强。
    前提条件:
        1:有嵌套
        2:有引用
        3:有返回
        4:有额外功能
    装饰器的用法:
        写法1:传统写法
            变量名 = 装饰器名(要被装饰的原函数名)
            变量名()       # 执行的就是,装饰后的原函数
        写法2:语法糖         语法糖 = 语法格式简化版
            在定义原函数的时候,加上@装饰器名即可,之后就能正确调用该原函数即可
"""
# 需求:发表评论前,需要先登录(模拟的操作)
# 1:定义装饰器(闭包函数),用于对指定函数功能做增强。
def login(fn_name):       #有外部函数
    # 定义内部函数,用于对指定的fn_name函数功能做增强。
    def inner():                #有内部函数
        print("登录中...登录成功....") #有额外功能
        fn_name()               #有引用
    return inner                #有返回
# # 写法1:传统写法
# def comment():
#     print("发表评论...")
# 写法2:语法糖
# comment()上面加一个@check_login修饰符的含义是:将comment函数名作为check_login函数的参数传递给check_login函数,并返回check_login函数的返回值
@login
def comment():
    print("发表评论...")
if __name__ == '__main__':
    # 2:调用装饰器,并传入要被装饰的函数名
    # 写法1:传统写法
    # comment = check_login(comment)
    # comment()
    # 写法2:语法糖, 在定义原函数的时候,加上@装饰器名即可,之后就能正确调用该原函数即可
    comment()
# 输出结果为
登录中...登录成功....
发表评论...

2.1装饰器的使用

函数分类:

1.无参无返回值的函数

    定义: def 函数名(): ...
    调用: 函数名()

# 需求:定义无参无返回值的原函数:get_sum(), 用于计算两个变量的和
def print_info(fn_name):
    def inner():
        print("【友好提示】正在努力计算中...")
        fn_name()
    return inner
@print_info
def get_sum():
    a = 10
    b = 20
    sum = a +b
    print("两个变量的和为:%d" % sum)
if __name__ == '__main__':
    get_sum()
# 输出结果
【友好提示】正在努力计算中...
两个变量的和为:30

2有参无返回值的函数

    定义: def 函数名(形参): ...
    调用: 函数名(实参)

# 需求:定义有参无返回值的原函数:get_sum(), 用于计算两个变量的和
def print_info(fn_name):
    def inner(a, b):
        print("【友好提示】正在努力计算中...")
        fn_name(a, b)
    return inner
@print_info
def get_sum(a, b):
    sum = a + b
    print("两个变量的和为:%d" % sum)
if __name__ == '__main__':
    get_sum(10, 20)
#输出结果
【友好提示】正在努力计算中...
两个变量的和为:30

3.无参有返回值的函数

    定义: def 函数名(): ... return 返回值
    调用: 用变量接收返回值 = 函数名()

def print_info(fn_name):
    def inner():
        print("【友好提示】正在努力计算中...")
        return fn_name()
    return inner
@print_info
def get_sum():
    a = 10
    b = 20
    sum = a + b
    return sum
if __name__ == '__main__':
    sum = get_sum()
    print(sum)
# 输出结果
【友好提示】正在努力计算中...
30

4.有参有返回值的函数

    定义: def 函数名(形参): ... return 返回值
    调用: 用变量接收返回值 = 函数名(实参)

# 需求:定义有参无返回值的原函数:get_sum(), 用于计算两个变量的和
def print_info(fn_name):
    def inner(a, b):
        print("【友好提示】正在努力计算中...")
        return fn_name(a, b)
    return inner
@print_info
def get_sum(a, b):
    sum = a + b
    return sum
if __name__ == '__main__':
    sum = get_sum(10, 30)
    print(sum)
# 输出结果
具体执行步骤:
    1:当使用 @print_info 装饰 get_sum 时,相当于执行了:getSum = print_info(get_sum)
    2:此时,print_info(get_sum) 返回的是 inner 函数。所以,getSum 现在实际上是 inner 函数。
    3:当调用 getSum(10, 30) 时,实际上调用的是 inner(10, 30)。
    4:在 inner 函数内部,先打印提示信息,然后调用原始的函数(即之前的 get_sum,被装饰前的函数)并传入参数,得到结果,然后返回。
【友好提示】正在努力计算中...
40

2.2通用版本的装饰器

以后所有装饰器以此为准

'''
通用装饰器:① 有嵌套 ② 有引用 ③ 有返回 ④ 有不定长参数 ⑤ 有return返回值
'''
def logging(fn):
    def inner(*args, **kwargs):
        # 输出装饰器功能
        print('-- 正在努力计算 --')
        # 调用fn函数
        return fn(*args, **kwargs)
    return inner
@logging
def sum_num1(a, b):
    result = a + b
    return result
print(sum_num1(20, 10))
@logging
def sum_num2(a, b, c):
    result = a + b + c
    return result
print(sum_num2(10, 20, 30))
# 输出结果
-- 正在努力计算 --
30
-- 正在努力计算 --
60

2.3多个装饰器装饰一个函数

"""
多个装饰器装饰1个函数,细节如下:
    多个装饰器装饰1个函数,装饰的顺序是由最贴近函数的那个开始,自下而上(执行顺序)
"""
# 需求:发表评论之前,需要先登录、在进行验证码验证,在不改变原有函数的基础上,对功能进行增强
# 1: 定义装饰器。加入:登录的功能 登录-》验证码-》发表评论
def check_login(fn_name):       #有外部函数
    # 定义内部函数,用于对指定的fn_name函数功能做增强。
    def inner():                #有内部函数
        print("登录中...") #有额外功能
        fn_name()               #有引用
    return inner                #有返回
# 2: 定义装饰器。加入:验证码的功能
def check_code(fn_name):       #有外部函数
    # 定义内部函数,用于对指定的fn_name函数功能做增强。
    def inner():                #有内部函数
        print("验证码验证中...") #有额外功能
        fn_name()               #有引用
    return inner                #有返回
# 3:定义被装饰的函数【自上而下】,意思是最上面的先装饰,让后往下
@check_login
@check_code
def comment():
    print("发表评论...")
if __name__ == '__main__':
#     # 写法1:传统的写法【自下而上】
#     code = check_code(comment)
#     login = check_login(code)
#     login()
#
    print("-" * 50)
    # 写法2:语法糖
    comment()
# 输出结果
登录中...
验证码验证中...
发表评论...

2.4带有参数的装饰器

"""
带有参数的装饰器:
    1个装饰器的参数只能有1个
"""
# 需求:定义一个装饰器减法运算,又能装饰加法运算的装饰器
def logging(flag):
    def print_info(fn_name):
        def inner(a, b):
            if flag == "+":
                print("【友好提示】正在努力 加法 计算中...")
            elif flag == "-":
                print("【友好提示】正在努力 减法 运算中...")
            fn_name(a, b)
        return inner
    return print_info
@logging("+")
def add(a, b):
    sum = a + b
    print(f"【加法运算】结果为:{sum}")
@logging("-")
def substract(a, b):
    sum = a - b
    print(f"【减法运算】结果为:{sum}")
if __name__ == '__main__':
    add(10, 20)
    substract(20, 10)
# 输出结果
【友好提示】正在努力 加法 计算中...
【加法运算】结果为:30
【友好提示】正在努力 减法 运算中...
【减法运算】结果为:10

3.深浅拷贝

1、几个概念

(该对象所指向的内存中的值不能被改变。当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。)

(该对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的地址,通俗点说就是原地改变。)

当我们写:

a = "python"

Python解释器干的事情:

① 创建变量a

② 创建一个对象(分配一块内存),来存储值 'python'

③ 将变量与对象,通过指针连接起来,从变量到对象的连接称之为引用(变量引用对象)

2、赋值

赋值: 只是复制了新对象的引用,不会开辟新的内存空间。

并不会产生一个独立的对象单独存在,只是将原有的数据块打上一个新标签,所以当其中一个标签被改变的时候,数据块就会发生变化,另一个标签也会随之改变。

3.浅拷贝

浅拷贝之所以称为浅拷贝,是它仅仅只拷贝了一层,拷贝了最外围的对象本身,内部的元素都只是拷贝了一个引用而已。

案例1:赋值

案例2:可变类型浅拷贝

案例3:不可变类型浅拷贝

注:不可变类型进行浅拷贝不会给拷贝的对象开辟新的内存空间,而只是拷贝了这个对象的引用

浅拷贝有三种形式: 切片操作,工厂函数(list()),copy模块中的copy函数。

如: lst = [1,2,[3,4]]

切片操作:lst1 = lst[:] 或者 lst1 = [each for each in lst]

注:[:]它与[0:]相似,意思是从0索引拆分到末尾。它返回一个新列表。

工厂函数:lst1 = list(lst)

copy函数:lst1 = copy.copy(lst)

但是在lst中有一个嵌套的list[3,4],如果我们修改了它,情况就不一样了。

1.当浅复制的值是不可变对象(字符串、元组、数值类型)时和“赋值”的情况一样,对象的id值(id()函数用于获取对象的内存地址)与浅复制原来的值相同。

2.当浅复制的值是可变对象(列表、字典、集合)时会产生一个“不是那么独立的对象”存在。有两种情况:

①第一种情况:复制的对象中无复杂子对象,原来值的改变并不会影响浅复制的值,同时浅复制的值改变也并不会影响原来的值。原来值的id值与浅复制原来的值不同。

②第二种情况:复制的对象中有复杂子对象(例如列表中的一个子元素是一个列表),如果不改变其中复杂子对象,浅复制的值改变并不会影响原来的值。 但是改变原来的值中的复杂子对象的值会影响浅复制的值。

4、深拷贝

深拷贝:和浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。深拷贝出来的对象是一个全新的对象,不再与原来的对象有任何关联。

所以改变原有被复制对象不会对已经复制出来的新对象产生影响。只有一种形式,copy模块中的deepcopy函数。

可变类型深拷贝:

不可变类型深拷贝:不可变类型进行深拷贝不会给拷贝的对象开辟新的内存空间,而只是拷贝了这个对象的引用

5、案例演示

案例1:对于可变对象深浅拷贝

import copy
a=[1,2,3]
print("=====赋值=====")
b=a
print(a)
print(b)
print(id(a))
print(id(b))
print("=====浅拷贝=====")
b=copy.copy(a)
print(a)
print(b)
print(id(a))
print(id(b))
print("=====深拷贝=====")
b=copy.deepcopy(a)
print(a)
print(b)
print(id(a))
print(id(b))
# 结果如下
"""
=====赋值=====
[1, 2, 3]
[1, 2, 3]
37235144
37235144
=====浅拷贝=====
[1, 2, 3]
[1, 2, 3]
37235144
37191432
=====深拷贝=====
[1, 2, 3]
[1, 2, 3]
37235144
37210184
"""

小结:

赋值: 值相等,地址相等

copy浅拷贝:值相等,地址不相等

deepcopy深拷贝:值相等,地址不相等

案例2:对于可变对象深浅拷贝(外层改变元素)

import copy
l=[1,2,3,[4, 5]]
l1=l #赋值
l2=copy.copy(l) #浅拷贝
l3=copy.deepcopy(l) #深拷贝
l.append(6)
print(l)  
print(l1)
print(l2)
print(l3)
# 结果
"""
[1, 2, 3, [4, 5], 6]     #l添加一个元素6
[1, 2, 3, [4, 5], 6]     #l1跟着添加一个元素6
[1, 2, 3, [4, 5]]        #l2保持不变
[1, 2, 3, [4, 5]]        #l3保持不变
"""

案例3:对于可变对象深浅拷贝(内层改变元素)

import copy
l=[1,2,3,[4, 5]]
l1=l #赋值
l2=copy.copy(l) #浅拷贝
l3=copy.deepcopy(l) #深拷贝
l[3].append(6) 
print(l) 
print(l1)
print(l2)
print(l3)
# 结果
"""
[1, 2, 3, [4, 5, 6]]      #l[3]添加一个元素6
[1, 2, 3, [4, 5, 6]]      #l1跟着添加一个元素6
[1, 2, 3, [4, 5, 6]]      #l2跟着添加一个元素6
[1, 2, 3, [4, 5]]         #l3保持不变
"""

小结:

① 外层添加元素时,浅拷贝不会随原列表变化而变化;内层添加元素时,浅拷贝才会变化。

② 无论原列表如何变化,深拷贝都保持不变。

③ 赋值对象随着原列表一起变化。

6.深浅拷贝相同点:

1.对于不可变类型,深浅拷贝都是失败,只是引用传递

2.对于可变类型第一层,深浅拷贝都会成功,都会开辟新的内存空间

7.深层拷贝不同点

1.对于可变类型第二层及以上,浅拷贝失败,只是引用传递

2.对于可变类型第二层第二层及以上,深拷贝都会成功,都会开辟新的内存空间

8.判断是否拷贝成功

1.看拷贝后是否开辟了新的空间,如果开辟了就是拷贝成功

2,拷贝成功后,原始和拷贝内容互不影响

3,浅拷贝最多拷贝一层,深拷贝可以拷贝多层

到此这篇关于Python中的闭包装饰器和深浅拷贝案例演示的文章就介绍到这了,更多相关python闭包装饰器和深浅拷贝内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文