python语言基础(六)--深浅拷贝、闭包与装饰器

一、深浅拷贝

1、概述

1. 所谓的深浅拷贝, 指的是: 拷贝的多与少.  深拷贝拷贝的多, 浅拷贝拷贝的少.
2. 深浅拷贝都可以操作可变 和 不可变类型, 但是深浅拷贝一般不会操作不可变类型.
3. 回顾可变和不可变类型, 划分依据: 在不改变地址值的情况下, 是否可以修改内容, 可以 => 可变类型, 不可以 => 不可变类型.
    可变类型:   列表, 字典, 集合
    不可变类型: 字符串, 整数, 浮点型, 元组, 布尔...
4. 所谓的深浅拷贝, 指的就是 copy 模块的不同函数.
    浅拷贝: copy.copy()
    深拷贝: copy.deepcopy()

2、浅拷贝

(一)、可变类型

概述

浅拷贝只拷贝第一层数据,深层次不拷贝。因此如果第一层数据改变,原列表改变,浅拷贝列表无变化;如果二层数据改变,浅拷贝的数据会发生变化。

例如

def dm02_浅拷贝可变类型():
    a = [1, 2, 3]
    b = [11, 22, 33]
    c = [6, 7, a, b]

    # 测试1 id(c)和id(d)
    d = copy.copy(c)        # 浅拷贝 = 只会拷贝 可变类型的 第1层数据.
    print('id(c)-->', id(c))    # 0x03
    print('id(d)-->', id(d))    # 0x04
    print("id(c)和id(d)值不一样, 说明浅拷贝第1层(最外面一层的数据)")

    # 测试2
    print(id(c[2]))     # 0x01
    print(id(a))        # 0x01
    print("id(c[2])和id(a)值一样, 说明浅拷贝第2层的数据")

    # 修改a[2] = 22
    a[2] = 22
    c[0] = 100
    print('c->', c)     # [100, 7, [1, 2, 22], [11, 22, 33]]
    print('d->', d)     # [6, 7, [1, 2, 22], [11, 22, 33]]

(二)、不可变类型

概述

不会给拷贝的对象开辟新的内存空间,只拷贝了这个对象的引用。

例如

def dm03_浅拷贝不可变类型():

    # 不可变类型 a b c
    a = (1, 2, 3)
    b = (11, 22, 33)
    c = (6, 7, a, b)

    d = copy.copy(c)
    print('id(c)-->', id(c))    # 0x03
    print('id(d)-->', id(d))    # 0x03
    print("id(c)和id(d)值一样, 说明c和d指向相同的内存空间")

3、 深拷贝

(一)、可变类型

概述

开辟新的内存空间,所有层都会深拷贝。所以不管是嵌套多少层的数据,都会独立开辟新的空间,之前存储的数据与新数据没有关系了。

作用

能保证数据的安全

例如

def dm04_深拷贝可变类型():
    a = [1, 2, 3]
    b = [11, 22, 33]
    c = [6, 7, a, b]

    d = copy.deepcopy(c)
    print('id(c)-->', id(c))    # 0x03
    print('id(d)-->', id(d))    # 0x04


    a[1] = 100
    b[1] = 800
    c[0] = 600
    print(f'c: {c}')    # [600, 7, [1, 100, 3], [11, 800, 33]]
    print(f'd: {d}')    # [6, 7, [1, 2, 3], [11, 22, 33]]

(二)、不可变类型

概述

直接就引用了, 不开辟新的内存空间

例如

def dm05_深拷贝不可变类型():
    a = (1, 2, 3)
    b = (11, 22, 33)
    c = (a, b)

    d = copy.deepcopy(c)
    print(id(c))    # 0x03
    print(id(d))    # 0x03
    print("c/d内存空间相同, 说明c和d指向相同的内存空间")

二、闭包与装饰器

1、函数名的作用

概述

  • Python是一门以 面向对象为基础的语言, 一切皆对象, 所以: 函数名也是对象.

  • 直接打印函数名, 打印的是函数的地址. 函数名()则是在调用函数.

  • 函数名可以作为对象使用, 所以它可以像变量一样赋值, 且赋值后的 变量名() 和 调用 原函数名() 效果是一样的.

作用

  • 函数名可以作为对象, 像变量那样赋值.

  • 函数名可以作为实参进行传递.

例如

# 1. 定义1个无参函数method()
def method():
    print('我是 method 函数')

def get_sum(a, b):
    print('get_sum函数: 加法运算')
    return a + b

def get_subtract(a, b):
    print('get_subtract函数: 减法运算')
    return a - b

# 2. 定义1个带1个参数的函数func()
def func(fn_name):
    """
    接收函数名(函数对象), 然后调用该函数的.
    :param fn_name: 接收到的函数名(即: 函数对象)
    :return: 无
    """
    print('我是 func 函数')
    fn_name()

# 模拟自定义的计算器.
def my_calculation(a, b, fn_name):
    """
    接收两个整数 和 具体的计算规则, 对整数进行对应的计算.
    :param a: 要操作的第1个整数
    :param b: 要操作的第2个整数
    :param fn_name: 具体的计算规则
    :return: 计算结果.
    """
    return fn_name(a, b)

# 3. 在main函数中测试.
if __name__ == '__main__':
    # 演示: 函数名可以作为实参进行传递
    func(method)
    print('-' * 21)

    # 加法
    result1 = my_calculation(10, 3, get_sum)
    print(f'result1: {result1}')    # 13
    print('-' * 21)

    # 减法
    result2 = my_calculation(10, 3, get_subtract)
    print(f'result2: {result2}')    # 7

2、闭包

概述

属于python中独有的一种写法,可以实现对外部函数的局部变量进行临时存储。

作用

可以"延长"函数内 局部变量的生命周期

构成条件

  • 有嵌套. 外部函数内要嵌套 内部函数.

  • 有引用. 在内部函数中使用 外部函数的变量.

  • 有返回. 在外部函数中, 返回 内部函数名, 即: 内部函数对象

格式

def 外部函数名(形参列表):
            ......
            def 内部函数名(形参列表):        # 有嵌套
                ......
                使用外部函数的变量           # 有引用
            return 内部函数名              # 有返回

目的

为了给 装饰器做铺垫, 因为装饰器的底层就是 闭包

例如

# 需求1: 定义1个函数用于保存变量10, 然后调用函数返回值并重复累加数值, 观察结果.
# 1. 定义函数.
def fn():
    return 10

# 需求2: 定义1个用于求和的闭包, 外部函数有num1参数, 内部函数有num2参数, 然后调用, 求两数之和, 观察结果.
def fn_outer(num1):         # 外部函数
    def fn_inner(num2):     # 内部函数, 有嵌套
        # 具体的求和动作.
        sum = num1 + num2   # 有引用, 内部函数中, 使用了外部函数的变量 num1
        # 打印结果.
        print(f'求和结果: {sum}')
    return fn_inner         # 有返回

# 在main函数中测试.
if __name__ == '__main__':
    # 3.测试: 非闭包写法.
    print(fn() + 1)     # 11
    print(fn() + 1)     # 11
    print(fn() + 1)     # 11
    print('-' * 21)

    # 4. 测试: 闭包的写法.
    my_fn = fn_outer(10)
    my_fn(1)    # 11
    my_fn(1)    # 11
    my_fn(1)    # 11

3、nonlocal关键字

概述

它可以实现在 内部函数中, 修改外部函数的 变量值

用法

类似于global关键字

例如

# 1. 定义函数, 实现 内部函数访问外部函数的变量值.
def fn_outer():     # 外部函数
    a = 100         # 外部函数的局部变量
    def fn_inner(): # 内部函数, 有嵌套.
        # 在内部函数中, 修改外部函数的变量值, 需要通过 nonlocal 关键字实现.
        nonlocal a
        a = a + 1
        # 在内部函数中, 访问外部函数的变量
        print(f"a: {a}")        # 有引用

    return fn_inner             # 有返回


# 在main函数中测试调用.
if __name__ == '__main__':
    fn = fn_outer()     # fn = fn_inner  等价于 内部函数.
    fn()    # 101
    fn()    # 102
    fn()    # 103

4、装饰器

(一)、介绍

概述

装饰器 也是 闭包的一种形式(写法), 即: 装饰器 = 闭包 + 额外功能

目的

在不改变原有函数的基础上, 对其功能做增强(扩展).

前提

  • 有嵌套.

  • 有引用.

  • 有返回值.

  • 有额外功能.

格式

def 外部函数名(形参列表):
            .....
            def 内部函数名(形参列表):        # 有嵌套
                功能扩展                   # 有额外功能
                ...
                使用外部函数的变量           # 有引用
            return 内部函数名              # 有返回

用法

方式1: 传统用法
变量名 = 装饰器名(被装饰的函数名)
变量名()

方式2: 语法糖, @标记符实现
在要被装饰的函数上, 写: @装饰器名, 之后就跟普通调用函数方式一样, 该怎么调用就怎么调用

例如

# 1. 定义函数, 充当装饰器, 用来给函数增加: 登陆功能(额外功能).
def check_login(fn_name):
    def fn_inner():         # 有嵌套
        print('登陆中...')   # 有额外功能
        fn_name()           # 有引用
    return fn_inner         # 有返回

# 2. 定义函数, 表示: 发表评论.
@check_login
def comment():
    print('发表评论!...')


# 在main函数中调用.
if __name__ == '__main__':
    # 3. 普通写法, 直接调用函数.
    # comment()

    # 4. 装饰器, 用法1: 传统格式.
    # comment = check_login(comment)
    # comment()

    # 5. 装饰器, 用法2: 语法糖格式.
    comment()

(二)、装饰无参无返回值的函数

装饰器的内置函数 格式要和 被装饰的函数 格式保持一致。被装饰的函数(原函数) 是无参无返回值的, 则: 装饰器的内置函数 也是 无参无返回值。

# 需求: 在无参无返回值的原有求和函数前, 添加1个友好提示, 即: 正在努力计算中...

# 1. 装饰器.
def print_info(fn_name):    # fn_name: 要被装饰的函数名(原函数名)
    def fn_inner():         # 有嵌套.  装饰器的内部函数 格式 要和 原函数 格式保持一致.
        print('[友好提示] 正在努力计算中...')  # 有额外功能
        fn_name()                          # 有引用
    return fn_inner         # 有返回


# 2. 要被装饰的函数, 即: 原函数(这里是: 无参无返回值的)
@print_info
def get_sum():
    a = 10
    b = 20
    # 求和
    sum = a + b
    # 打印结果.
    print(f'sum: {sum}')

# 在main函数中测试调用.
if __name__ == '__main__':
    # 装饰器 写法1: 传统写法.
    # get_sum = print_info(get_sum)
    # get_sum()

    # 装饰器 写法2: 语法糖
    get_sum()

(三)、装饰有参无返回值的函数

装饰器的内置函数 格式要和 被装饰的函数 格式保持一致。被装饰的函数(原函数) 是有参无返回值的, 则: 装饰器的内置函数 也是 有参无返回值。

# 需求: 在有参无返回值的原有求和函数前, 添加1个友好提示, 即: 正在努力计算中...

# 1. 装饰器.
def print_info(fn_name):    # fn_name: 要被装饰的函数名(原函数名)
    def fn_inner(a, b):         # 有嵌套.  装饰器的内部函数 格式 要和 原函数 格式保持一致.
        print('[友好提示] 正在努力计算中...')  # 有额外功能
        fn_name(a, b)                      # 有引用
    return fn_inner         # 有返回


# 2. 要被装饰的函数, 即: 原函数(这里是: 有参无返回值的)
@print_info
def get_sum(a, b):
    # 求和
    sum = a + b
    # 打印结果.
    print(f'sum: {sum}')

# 在main函数中测试调用.
if __name__ == '__main__':
    # 装饰器 写法1: 传统写法.
    # get_sum = print_info(get_sum)
    # get_sum(100, 200)

    # 装饰器 写法2: 语法糖
    get_sum(10, 3)

(四)、装饰无参有返回值的函数

装饰器的内置函数 格式要和 被装饰的函数 格式保持一致。被装饰的函数(原函数) 是无参有返回值的, 则: 装饰器的内置函数 也是 无参有返回值。

# 需求: 在无参有返回值的原有求和函数前, 添加1个友好提示, 即: 正在努力计算中...

# 1. 装饰器.
def print_info(fn_name):  # fn_name: 要被装饰的函数名(原函数名)
    def fn_inner():  # 有嵌套.  装饰器的内部函数 格式 要和 原函数 格式保持一致.
        print('[友好提示] 正在努力计算中...')  # 有额外功能
        return fn_name()  # 有引用

    return fn_inner  # 有返回


# 2. 要被装饰的函数, 即: 原函数(这里是: 无参有返回值的)
@print_info
def get_sum():
    a, b = 10, 3
    # 求和
    sum = a + b
    # 返回结果.
    return sum


# 在main函数中测试调用.
if __name__ == '__main__':
    # 装饰器 写法1: 传统写法.
    # get_sum = print_info(get_sum)
    # sum1 = get_sum()
    # print(f'sum1: {sum1}')

    # 装饰器 写法2: 语法糖
    sum2 = get_sum()
    print(f'sum2: {sum2}')

(五)、装饰有参有返回值的函数

装饰器的内置函数 格式要和 被装饰的函数 格式保持一致。被装饰的函数(原函数) 是有参有返回值的, 则: 装饰器的内置函数 也是 有参有返回值。

# 需求: 在有参有返回值的原有求和函数前, 添加1个友好提示, 即: 正在努力计算中...

# 1. 装饰器.
def print_info(fn_name):  # fn_name: 要被装饰的函数名(原函数名)
    def fn_inner(a, b):  # 有嵌套.  装饰器的内部函数 格式 要和 原函数 格式保持一致.
        print('[友好提示] 正在努力计算中...')  # 有额外功能
        return fn_name(a, b)  # 有引用

    return fn_inner  # 有返回


# 2. 要被装饰的函数, 即: 原函数(这里是: 有参有返回值的)
@print_info
def get_sum(a, b):
    # 求和
    sum = a + b
    # 返回结果.
    return sum


# 在main函数中测试调用.
if __name__ == '__main__':
    # 装饰器 写法1: 传统写法.
    # get_sum = print_info(get_sum)
    # sum1 = get_sum(10, 20)
    # print(f'sum1: {sum1}')

    # 装饰器 写法2: 语法糖
    sum2 = get_sum(11, 22)
    print(f'sum2: {sum2}')

(六)、装饰不定长参数函数

装饰器的内置函数 格式要和 被装饰的函数 格式保持一致。被装饰的函数(原函数) 是不定长参数, 则: 装饰器的内置函数 也是 不定长参数。

# 需求: 定义一个可以计算 多个数据 和 多个字典value值之和的 函数, 并调用.
# 要求: 在原有函数的计算结果之前, 加一个友好提示: 正在努力计算中.


# 1. 定义装饰器.
def print_info(fn_name):
    def fn_inner(*args, **kwargs):          # 有嵌套
        print('[友好提示]正在努力计算中...')    # 有额外功能.
        return fn_name(*args, **kwargs)     # 有引用
    return fn_inner                         # 有返回


# 2. 定义原函数, 表示具体的累加动作.
# @print_info
def get_sum(*args, **kwargs):  # (1, 2, 3, 4, 5),  {'a':1, 'b':2, 'c':3}
    # 2.1 定义求和变量, 记录求和结果.
    sum = 0

    # 2.2 回顾: *args, 接收所有的 位置参数, 封装成 元组, 即: (1, 2, 3)
    # 所以这里 等于 元组求和.
    for value in args:
        sum += value

    # 2.3 回顾: **kwargs, 接收所有的 键值对参数, 封装成 字典, 即: {'a':1, 'b':2, 'c':3}
    # 所以这里 等于 字典value值求和.
    for value in kwargs.values():
        sum += value

    # 2.4 返回求和计算结果.
    return sum


# 3. 在main函数中测试.
if __name__ == '__main__':
    # 4. 装饰器, 写法1: 传统方式.
    get_sum = print_info(get_sum)
    sum = get_sum(11, 22, 33, a=1, b=2, c=3)
    print(f'求和结果: {sum}')

    # 5. 装饰器, 写法2: 语法糖.
    # sum = get_sum(11, 22, 33, a=1, b=2, c=3)
    # print(f'求和结果: {sum}')

(七)、多个装饰器装饰一个函数

注意

1. 多个装饰器装饰1个函数时, 会按照 由内到外的 顺序进行装饰, 即: 就近原则.
2. 简单记忆:
    由内到外装饰(针对于: 传统写法),  从上往下执行(针对于: 语法糖写法).

代码

# 需求: 发表评论前, 需要先登录, 再进行验证码校验.

# 1. 定义装饰器, 增加 登陆功能.
def check_login(fn_name):
    def fn_inner():                 # 有嵌套
        print('登陆校验中!.....')     # 有额外功能.
        fn_name()                   # 有引用
    return fn_inner                 # 有返回


# 2. 定义装饰器, 增加 验证码校验功能.
def check_code(fn_name):
    def fn_inner():                 # 有嵌套
        print('验证码校验中!.....')     # 有额外功能.
        fn_name()                   # 有引用
    return fn_inner                 # 有返回


# 3. 定义原函数, 表示: 发表评论.
@check_login
@check_code
def comment():
    print("发表评论!")

# 在main函数中, 测试.
if __name__ == '__main__':
    # 4. 测试: 装饰器的写法1 传统写法.
    cc = check_code(comment)    # 增加: 验证码校验功能.
    cl = check_login(cc)        # 增加: 登陆功能.
    cl()
    print('-' * 21)

    # 5. 测试: 装饰器的写法2 语法糖.
    comment()

(八)、带有参数的装饰器

注意

1. 一个装饰器的参数只能有1个.
2. 如果装饰器有多个参数, 则可以在该装饰器的外边再定义1个函数, 用于封装多个参数, 并返回一个装饰器.

例如

# 需求: 定义1个既能装饰加法运算, 也能装饰减法运算的装饰器, 即: 带有参数的装饰器, 并测试.

# 1. 定义装饰器, 能装饰 加法, 减法运算.
def logging(flag):
    def decorator(fn_name):
    # def decorator(fn_name, flag):     # decorator: 装饰的意思,  这行代码会报错, 因为装饰器的参数只能有1个.
        def fn_inner():         # 有嵌套
            if flag == '+':
                print('[友好提示] 正在努力计算 加法 中...')  # 有额外功能
            elif flag == '-':
                print('[友好提示] 正在努力计算 减法 中...')  # 有额外功能
            fn_name()           # 有引用
        return fn_inner         # 有返回
    return decorator            # 有返回

# 2. 定义原函数, 表示: 加法运算.
@logging('+')
def add():
    a, b = 10, 3
    sum = a + b
    print(f'求和结果: {sum}')


# 3. 定义原函数, 表示: 减法运算.
@logging('-')
def subtract():
    a, b = 22, 11
    sub = a - b
    print(f'求差结果: {sub}')

# 在main函数中测试调用.
if __name__ == '__main__':
    # 4. 测试加法.
    add()
    print('-' * 21)

    # 5. 测试减法.
    subtract()

优化版本

拓展:Python中的每个函数(对象)都有一个属性 name, 可以获取该函数的 名字

# 需求: 定义1个既能装饰加法运算, 也能装饰减法运算的装饰器, 即: 带有参数的装饰器, 并测试.

# 1. 定义装饰器, 能装饰 加法, 减法运算.
def decorator(fn_name):
    def fn_inner():         # 有嵌套
        # fn_name = 函数名 = 函数对象.
        # print(fn_name)      # 地址值, 说明 fn_name = 函数对象
        # print(fn_name.__name__) # 根据函数对象, 获取该函数的名字.

        if fn_name.__name__ == 'add':
            print('[友好提示] 正在努力计算 加法 中...')  # 有额外功能
        elif fn_name.__name__ == 'subtract':
            print('[友好提示] 正在努力计算 减法 中...')  # 有额外功能
        fn_name()           # 有引用
    return fn_inner         # 有返回

# 2. 定义原函数, 表示: 加法运算.
@decorator
def add():
    a, b = 10, 3
    sum = a + b
    print(f'求和结果: {sum}')


# 3. 定义原函数, 表示: 减法运算.

@decorator
def subtract():
    a, b = 22, 11
    sub = a - b
    print(f'求差结果: {sub}')

# 在main函数中测试调用.
if __name__ == '__main__':
    # 4. 测试加法.
    add()
    print('-' * 21)

    # 5. 测试减法.
    subtract()

你可能感兴趣的:(Python语法,python,开发语言)