带你从入门到精通——Python(十一. 闭包、装饰器和深浅拷贝)

建议先阅读我之前的博客,掌握一定的Python前置知识后再阅读本文,链接如下:

带你从入门到精通——Python(一. 基础知识)-CSDN博客

带你从入门到精通——Python(二. 判断语句和循环语句)-CSDN博客

带你从入门到精通——Python(三. 函数基础)-CSDN博客

带你从入门到精通——Python(四. 五大容器一)-CSDN博客

带你从入门到精通——Python(五. 五大容器二)-CSDN博客

带你从入门到精通——Python(六. 函数进阶)-CSDN博客

带你从入门到精通——Python(七. 文件操作)-CSDN博客

带你从入门到精通——Python(八. 异常、模块和包)-CSDN博客

带你从入门到精通——Python(九. 面向对象一)-CSDN博客

带你从入门到精通——Python(十. 面向对象二)-CSDN博客

目录

十一. 闭包、装饰器和深浅拷贝

11.1 闭包

11.1.1 作用域

11.1.2 闭包概述

11.1.3 global关键字和nonlocal关键字

11.2 装饰器

11.2.1 装饰器概述

11.2.2 装饰器的使用方法

11.2.3 带参数的语法糖装饰器

11.3 深浅拷贝

11.3.1 不可变对象和可变对象

11.3.2 赋值

11.3.3 浅拷贝

11.3.4 深拷贝


十一. 闭包、装饰器和深浅拷贝

11.1 闭包

11.1.1 作用域

        在Python代码中,作用域可以分为有两种:全局作用域局部作用域

        一般来说,定义在main函数中的变量被称为全局变量,其作用域为全局作用域,而定义在在函数、代码块(例如if、for、while等语句块)中的变量被称为局部变量,其作用域为局部作用域,即对应的函数或代码块内部。

        在局部作用域中可以访问局部变量和全局变量,但在全局作用域中只能访问全局变量,这是由于在Python的底层存在垃圾回收机制,局部变量在对应的函数调用完毕或者对应的代码块执行完毕后,会被自动回收导致无法继续被访问,该机制能够回收内存空间,加快计算机的运行。

11.1.2 闭包概述

        使用了外部函数变量的内部函数称为闭包(Closure),闭包可以保存外部函数内定义的局部变量,使其不会随着外部函数的调用完毕而被回收,即可以在全局作用域中,实现间接对局部变量的访问,其语法格式如下:

def outer_func(outer_var):
    def inner_func(inner_var):
        use outer_var to do sth.
    return inner_func # inner_func没有执行,只是返回了inner_func的内存地址

closure = outer_function(outer_var) # 创建闭包
# 相当于把inner_func的内存地址赋值给变量closure

closure(inner_var) # 调用闭包
# 找到inner_func的内存地址并调用inner_func
# inner_func保留了outer_func的outer_var这个局部变量

        闭包的构成条件如下:

        1. 有嵌套:在外部函数内部定义一个内部函数,形成函数嵌套。

        2. 有引用:内部函数使用了在外部函数内定义的变量(包括外部函数所接收的参数)

        3. 有返回:外部函数的返回值为内部函数名。

11.1.3 global关键字和nonlocal关键字

        global关键字用于在局部作用域内声明一个变量为全局变量,用于修改全局变量的值。

        nonlocal关键字用于在局部作用域内声明一个变量为上一层函数中的局部变量,nonlocal关键字只能用于嵌套函数的内部函数中,用于修改上一层函数的局部变量的值。

11.2 装饰器

11.2.1 装饰器概述

        装饰器本质上是一个闭包的外部函数,其作用是不改变内部原有函数的基础上,给内部原有函数增加额外功能。

        因此,装饰器的构成条件如下:

        1. 有嵌套:在外部函数内部定义一个内部函数,形成函数嵌套。

        2. 有引用:内部函数使用了在外部函数内定义的变量(包括外部函数所接收的参数)

        3. 有返回:外部函数的返回值为内部函数名。

        4. 有额外功能:在不改变内部原有函数的基础上,给被装饰的内部原有函数增加额外功能。

11.2.2 装饰器的使用方法

        装饰器的使用方法有两种,第一种为传统闭包形式:

def outer(func):
    def inner(a, b):
        print('正在计算中') # 为get_sum函数添加的额外功能
        return func(a, b)
    return inner

def get_sum(x, y):
    return x + y

closure = outer(get_sum) # 创建闭包
ans = closure(1, 2) # 调用闭包
print(ans)
'''
正在计算中
3
'''

        第二种为语法糖形式,即在需要装饰的函数上一行添加@装饰器名的格式:

def outer(func):
    def inner(a, b):
        print('正在计算中') # 为get_sum函数添加的额外功能
        return func(a, b)
    return inner

@outer
def get_sum(x, y):
    return x + y

ans = get_sum(2, 6)
print(ans)
'''
正在计算中
8
'''

        注意:如果被装饰的原始函数有形参,则装饰器的内部函数也必须有形参,反之亦然;如果被装饰的原始函数有返回值,则装饰器的内部函数也必须有返回值,反之亦然。

        如果一个函数带有多个语法糖装饰器,则离函数最近的装饰器先装饰,然后上一个的装饰器再进行装饰,上一个由内到外的装饰过程。

11.2.3 带参数的语法糖装饰器

        如果想使用带参数的语法糖装饰器,需要在原始语法糖装饰器上再嵌套一层外部函数用于接收额外参数,因为直接与内部原始函数相邻的外部函数只能接收一个参数(即内部原始函数的函数名),具体示例代码如下:

def extra_args(operation, name):
    def decorator(func):
        def inner(x, y):
            if operation == '+':
                print(f'-- {name}正在努力进行加法运算 --')
            elif operation == '-':
                print(f'-- {name}正在努力进行减法运算 --')
            return func(x, y)
        return inner
    return decorator

@extra_args('+', '小明')
def add_nums(a, b):
    res = a + b
    return res

@extra_args('-', '小红')
def sub_nums(a, b):
    res = a - b
    return res

print(add_nums(10, 20))
print(sub_nums(60, 50))
'''
-- 小明正在努力进行加法运算 --
30
-- 小红正在努力进行减法运算 --
10
'''

        注意:上述带参数的语法糖装饰器的执行流程是先执行extra_args函数,将参数operation以及参数name保留在外层闭包函数decorator中,便于内部函数使用,之后则是正常执行闭包函数decorator的作用,为内部闭包函数inner添加额外功能。

11.3 深浅拷贝

11.3.1 不可变对象和可变对象

        不可变对象是指一旦创建就不可修改的对象,包括字符串、元组、数值类型(整型、浮点型等),这些对象在内存中的值不能被改变,当试图通过这些对象的引用来修改这些对象的值时,会把这些对于原来的值复制一份后再改变,同时会开辟一个新的内存地址来存储改变后的值,最后再由变量再次指向这个新的内存地址。

        可变对象是指可以修改的对象,包括列表、字典、集合,这些对象在内存中的值可以被改变。当试图通过这些对象的引用来修改这些对象的值时,会直接修改这些对象在内存中的值。

11.3.2 赋值

        当执行以下语句时,会为变量a进行赋值:

a = 'python'

        Python解释器首先会创建变量a(可以视为某个对象的一个标签),再创建一个对象(对象也就是被分配的一块内存,内存中存储该对象所代表的值)来存储字符串类型的数据'python',最后将变量与对象通过指针连接起来,从变量到对象的连接称之为引用(也称变量引用对象)。

        此时如果将变量a继续赋值给变量b,那么变量a和变量b会指向同一块内存地址,此时由于变量a属于不可变类型,因此改变变量b的值并不会影响a的值。

        但如果变量a是一个可变对象(例如列表),将变量a继续赋值给变量b后,变量a和变量b同样会指向同一块内存地址,但此时改变变量b内部的元素也会影响变量a内部的元素,反之亦然,示例如下:

# 不可变类型的赋值
a = 'python'
b = a
print(id(a), id(b))
print(a, b)
'''
2194773003312 2194773003312
python python
'''
b = 'PYTHON'
print(id(a), id(b))
print(a, b)
'''
2194773003312 2194773003248
python PYTHON
'''

# 可变类型的赋值
a = [1, 2, 3]
b = a
print(id(a), id(b))
print(a, b)
b[0] = 666
print(id(a), id(b))
print(a, b)
'''
2157874838272 2157874838272
[1, 2, 3] [1, 2, 3]
2157874838272 2157874838272
[666, 2, 3] [666, 2, 3]
'''

11.3.3 浅拷贝

        在Python中,浅拷贝可以使用copy模块下的copy函数、切片、类型转换函数(例如list())以及推导式(例如列表推导式)等方法来实现。

        对于不可变类型来说,浅拷贝与不可变类型之间的变量赋值是一样的,即浅拷贝前后的两个变量指向同一块内存地址,且改变其中一个变量的值并不会影响另一个变量。

        对于可变类型来说,浅拷贝会创建一个新的对象,即浅拷贝前后的两个变量指向不同的内存地址,但浅拷贝后的变量内部的元素依旧是原来浅拷贝前的变量内部的元素,如果浅拷贝前的变量内部的值全为不可变类型,那么改变其中一个变量内部的元素并不会影响另一个变量;但如果浅拷贝前的变量内部的元素有可变类型,那么改变其中一个变量内部的可变类型的元素则会影响另一个变量(修改一个变量内的不可变类型的元素依旧不会影响另一个变量),示例如下:

from copy import copy, deepcopy

# 不可变类型的浅拷贝
a = 'python'
b = copy(a)
print(id(a), id(b)) # id函数用于获取对象的内存地址
print(a, b)
'''
2469715397680 2469715397680
python python
'''
b = 'PYTHON'
print(id(a), id(b))
print(a, b)
'''
2469715397680 2469715397616
python PYTHON
'''

# 可变类型的浅拷贝, 修改不可变类型的元素不会影响原对象
a = [1, 2, 3, [4, 6]]
b = copy(a)
print(id(a), id(b))
print(a, b)
'''
2470409392192 2470409620544
[1, 2, 3, [4, 6]] [1, 2, 3, [4, 6]]
'''
b[0] = 666
print(id(a), id(b))
print(a, b)
'''
2470409392192 2470409620544
[1, 2, 3, [4, 6]] [666, 2, 3, [4, 6]]
'''

# 可变类型的浅拷贝, 修改可变类型的元素会影响原对象
a = [1, 2, 3, [4, 6]]
b = copy(a)
print(id(a), id(b))
print(a, b)
'''
2229164198976 2229163970880
[1, 2, 3, [4, 6]] [1, 2, 3, [4, 6]]
'''
b[-1][0] = 888
print(id(a), id(b))
print(a, b)
'''
2229164198976 2229163970880
[1, 2, 3, [888, 6]] [1, 2, 3, [888, 6]]
'''

11.3.4 深拷贝

        在Python中,深拷贝只能通过copy模块中的deepcopy函数实现。

        对于不可变类型来说,深拷贝与不可变类型之间的变量赋值是一样的,即深拷贝前后的两个变量指向同一块内存地址(注意:如果原对象是一个不可变类型,但其中的元素有可变类型,例如tup = (1,4,6,[2,4])的形式,此时深拷贝前后的两个变量将会指向不同的内存地址),且改变其中一个变量的值并不会影响另一个变量。

        对于可变类型来说,深拷贝会创建一个新的对象,即深拷贝前后的两个变量指向不同的内存地址,并且深拷贝会拷贝原对象内的所有元素,包括多层嵌套的元素,因此改变其中一个变量内部的任何元素都不会影响另一个变量,示例如下

from copy import copy, deepcopy

# 不可变类型的深拷贝
a = 'python'
b = deepcopy(a)
print(id(a), id(b))
print(a, b)
'''
1845612727344 1845612727344
python python
'''
b = 'PYTHON'
print(id(a), id(b))
print(a, b)
'''
1845612727344 1845612727280
python PYTHON
'''

# 可变类型的深拷贝, 修改不可变类型的元素不会影响原对象
a = [1, 2, 3, [4, 6]]
b = deepcopy(a)
print(id(a), id(b))
print(a, b)
'''
1846306459968 1846306688256
[1, 2, 3, [4, 6]] [1, 2, 3, [4, 6]]
'''
b[0] = 666
print(id(a), id(b))
print(a, b)
'''
1846306459968 1846306688256
[1, 2, 3, [4, 6]] [666, 2, 3, [4, 6]]
'''

# 可变类型的深拷贝, 修改可变类型的元素也不会影响原对象
a = [1, 2, 3, [4, 6]]
b = deepcopy(a)
print(id(a), id(b))
print(a, b)
'''
1846306687744 1846306459968
[1, 2, 3, [4, 6]] [1, 2, 3, [4, 6]]
'''
b[-1][0] = 888
print(id(a), id(b))
print(a, b)
'''
1846306687744 1846306459968
[1, 2, 3, [4, 6]] [1, 2, 3, [888, 6]]
'''

作者:梦想是成为算法高手

物联沃分享整理
物联沃-IOTWORD物联网 » 带你从入门到精通——Python(十一. 闭包、装饰器和深浅拷贝)

发表回复