大草原的小灰灰头像

Python函数、闭包、装饰器、类型注解介绍

1 函数概述

  • 函数也是对象。
  • 函数可以动态添加属性。
  • 函数可以赋值给变量。
  • 函数也可以作为参数。
  • 函数也可以作为返回值。
  • 代码示例
    •   # 1 函数也是对象
        print('-----------1----------')
        a1 = 100  # int类的实例对象
        a2 = 'hello'  # str类的实例对象
        a3 = [10, 20, 30]  # list类的实例对象
        
        
        def welcome():  # welcome函数是function类的实例对象
            print('hello world')
      
      
        print(type(a1))
        print(type(a2))
        print(type(a3))
        print(type(welcome))
        
        # 2 函数可以动态添加属性
        print('-----------2----------')
        welcome.desc = 'python'  # 添加属性
        welcome.version = 1.0  # 添加属性
        print(welcome.desc)  # 调用
        print(welcome.version)  # 调用
        
        # 3 函数可以赋值给变量
        print('-----------3----------')
        say_hello = welcome  # 赋值
        say_hello()  # 调用
        
        # 4 可变参数与不可变参数
        
        print('-----------4----------')
        ## 4.1 不可变参数
        print('----------4.1----------')
        b1 = 10088
        def welcome_f1(data):
            print('welcome_f1 data: ', data)
            data = 10011  # 此处data是新实例对象
            print('data: ', data)
        
        welcome_f1(b1)
        print('b1: ', b1)
        
        ## 4.2 可变参数
        print('----------4.2----------')
        b2 = [10, 20, 30]
        def welcome_f2(data):
            print('welcome_f2 data: ', data)
            data[2] = 99
            print('data: ', data)
        
        welcome_f2(b2)
        print('b2: ', b2)
        
        # 5 函数也可以作为参数
        print('-----------5----------')
        def welcome_param():
            print('welcome_param 调用')
        
        def caller(f):
            print('caller 调用')
            f()
        
        caller(welcome_param)
        
        
        # 6 函数也可以作为返回值
        print('-----------6----------')
        def welcome_return():
            print('welcome_return')
            def show_msg(msg):
                print('msg: ', msg)
            return show_msg
        
        res1 = welcome_return()
        res1('Python or C++')
      
    • 在这里插入图片描述

2 函数的多返回值

  • 在return关键字后面写多个值,并且多个值之间用逗号隔开,Python 会自动把多个值打包成元组。
  • 示例代码
    •   def calculate(x, y):
            res1 = x + y
            res2 = x - y
            return res1, res2
        
        result = calculate(10, 28)
        print('result: ', type(result), result)
        
        r1, r2 = calculate(10, 28)
        print('r1: ', type(r1), r1)
        print('r2: ', type(r2), r2)
      
    • 在这里插入图片描述

3 参数的打包与解包

  • 定义函数时,打包接收参数:
    • *形参名:打包所有的位置参数(会形成一个元组)。
    • **形参名 :打包所有的关键字参数(会形成一个字典)。
  • 调用函数时,解包传递参数:
    • *变量名:将元组拆解成一个一个独立的位置参数。
    • **变量名:将字典拆解一个一个key=value形式的关键字参数。
  • 代码示例
    •   # 打包接收参数
        # *args: 打包所有位置参数(会形成一个元组)
        # **kwargs: 打包所有关键字参数(会形成一个字典)
        print('------------打包接收参数-----------')
        def show_info1(*args, **kwargs):
            print('args: ', args)
            print('kwargs: ', kwargs)
        
        
        show_info1(10, 20, 30, name='张三', age=20)
        
        
        # 解包传递参数
        # *变量名: 将元组拆解成一个一个独立的位置参数
        # **变量名: 将字典拆解成一个一个 key=value 形式的关键字参数
        print('------------解包传递参数-----------')
        def show_info2(num1, num2, num3, name, age):
            print('num1: ', num1)
            print('num2: ', num2)
            print('num3: ', num3)
            print('name: ', name)
            print('age: ', age)
        
        
        nums = (1, 2, 3)
        person = {'name': '小明', 'age': 18}
        
        show_info2(*nums, **person)
        
        print('------------二者结合使用-----------')
        def show_info3(*args, **kwargs):
            print('args: ', args)
            print('kwargs: ', kwargs)
        
        nums = (1, 2, 3)
        person = {'name': '小明', 'age': 18}
        
        show_info2(*nums, **person)
      
    • 在这里插入图片描述

4 高阶函数

  • 当一个函数的『参数是函数』或者『返回值是函数』那该函数就是『高阶函数』。
  • 示例代码
    •   def info(msg):
            return '[提示]: ' + msg
        
        def warn(msg):
            return '[警告]: ' + msg
        
        def error(msg):
            return '[错误]: ' + msg
        
        def log(func, text):
            print(func(text))
        
        log(info, '文件保存成功')
        log(warn, '磁盘空间不足')
        log(error, '文件保存失败')
      
    • 在这里插入图片描述

5 条件表达式

  • 表达式:执行后最终能得到一个值的代码,就是表达式。表达式最终会形成一个值,可以写在任何一个需要值的地方。
  • 条件表达式: 根据不同的条件,得到不同的值,又称:三元表达式,也叫:三目运算符。
    • 语法格式:结果1 if 条件 else 结果2
    • 具体规则: 如果条件为真,整个表达式的结果就是“结果1”,否则就是“结果2”。
  • 示例代码
    •   # 表达式
        a1 = 3 + 5
        a2 = 'abc' * 2
        a3 = 5 > 3
        a4 = 'y' in 'python'
        a5 = len('hello world')
        
        # 条件表达式
        age = 18
        text='成年' if age >= 19 else '未成年'
        print('text:', text)
      
    • 在这里插入图片描述

6 匿名函数

  • 所谓『匿名函数』,就是没有名字的函数,它无需使用def关键字去定义。
  • Python 中使用lambda关键字来定义『匿名函数』,格式为:lambda 参数: 表达式。
  • 使用场景: 当一个函数只用一次、只做一点点小事,使用匿名函数会更简洁。
  • 匿名函数特点
    • 只能写一行,不能写多行代码。
    • 不能写代码块(if、for、while)
    • 冒号右边必须是表达式,且只能写一个表达式。
    • 执行结果自动作为返回值。
  • 示例代码
    •   # 使用普通函数实现计算效果
        def add(a, b):
            return a + b
        
        
        def sub(a, b):
            return a - b
        
        
        def calculate(func, x, y):
            print(f'计算结果为: {func(x, y)}')
        
        
        calculate(add, 2, 3)
        calculate(sub, 4, 5)
        
        # 匿名函数
        add2 = lambda a, b: a + b
        add3 = lambda: 'this is add3'
        
        res2 = add2(20, 48)
        print('res2:', res2)
        
        res3 = add3()
        print('res3:', res3)
        
        calculate(lambda a, b: a + b, 8, 9)
        
        # 应用
        is_adult = lambda age: '成年' if age >= 18 else '未成年'
        print(is_adult(23))
      
    • 在这里插入图片描述

7 几个数据处理函数

7.1 map函数

  • 对一组数据中的每一个元素,统一执行某种操作(加工),并生成一组新数据。
  • 示例代码
    •   print('------------示例1---------------')
        nums = [10, 20, 30, 40]
        def double_func(x):
            return x * 2
        
        # map函数返回值是一个迭代器对象,需要我们自己手动去遍历,或者手动类型转换
        res1 = map(double_func, nums)
        print('res1:', list(res1))
        
        # for item in res1:
        #     print(item)
        
        
        print('------------示例2---------------')
        names=('python', 'java', 'js')
        
        res2 = map(lambda x: x.upper(), names)
        print('res2:', tuple(res2))
        
        
        print('------------示例3---------------')
        str_number = {'12', '22', '32'}
        res3 = map(int, str_number)
        print('res3:', set(res3))
        
        # 注意点:
        # 1.延迟执行:map 不会立刻计算,只有在“需要结果”时才执行计算。
        # 2.返回的是迭代器对象,且一旦遍历完成,就会被“耗尽”。
        # 3.map不会影响元素数量。
        print('------------示例4---------------')
        nums2 = [10, 20, 30, 40]
        res4 = map(lambda x: x * 2, nums2)
        print('res4:', list(res4))
        print('res4:', list(res4))
      
    • 在这里插入图片描述

7.2 filter 函数

  • 从一组数据中,筛选出符合条件的元素(过滤),并组成一组新数据。
  • 示例代码
    •   print('------------示例1---------------')
        
        nums1 = [10, 20, 30, 40, 50]
        res1 = filter(lambda n: n > 30, nums1)
        print('res1:', list(res1))
        
        print('------------示例2---------------')
        person1 = [
            {'name': '张三', 'age': 16},
            {'name': '李四', 'age': 14},
            {'name': '王五', 'age': 20},
            {'name': '赵六', 'age': 25},
        ]
        
        res2 = filter(lambda p: p['age'] > 18, person1)
        print('res2:', list(res2))
        
        print('------------示例3---------------')
        names = ['张三', '', '李四', None, '王五']
        res3 = filter(lambda n: n, names)
        print('res3:', list(res3))
        
        # 注意点
        # 1.延迟执行:filter不会立刻筛选,只有在“需要结果”时才执行。
        # 2.返回的是迭代器对象,且一旦遍历完成就会被“耗尽”。
        # 3.filter可能会影响元素数量。
        
        # filter函数的特殊用法:如果不传递过滤函数,那么自动会过滤掉“假值”
        print('------------示例4---------------')
        data1 = [0, 1, '', 'hello', [], (1, 2), 5]
        res4 = filter(None, data1)
        print('res4:', list(res4))
      
    • 在这里插入图片描述

7.3 sorted 函数

  • 对一组数据进行排序,返回一组新数据。
  • 示例代码
    •   print('------------示例1---------------')
        # 默认从小到大排序, reverse=True设置从大到小排序
        nums1 = [100, 20, 3, 521, 222]
        res1 = sorted(nums1, reverse=True)
        print('res1:', res1)
        
        print('------------示例2---------------')
        # 按照字符串长度排序
        name1 = ['python', 'js', 'c++', 'java']
        res2 = sorted(name1, key=lambda n: len(n))
        # res2 = sorted(name1, key=len)
        print('res2:', res2)
        
        print('------------示例3---------------')
        # 根据字典中的某个字段进行排序
        person1 = [
            {'name': '张三', 'age': 16},
            {'name': '李四', 'age': 14},
            {'name': '王五', 'age': 20}
        ]
        res3 = sorted(person1, key=lambda p: p['age'])
        print('res3:', res3)
        
        
        print('------------示例4---------------')
        res4 = max(person1, key=lambda p: p['age'])
        print('res4:', res4)
      
    • 在这里插入图片描述

7.4 reduce 函数

  • 将一组数据不断“合并”,最终归并成一个结果。
  • 示例代码
    •   from functools import reduce
      
        print('------------示例1---------------')
        # 数值统计-求和
        nums1 = [1, 2, 3, 4, 5]
        
        def count_func(a, b):
            return a + b
        
        res1 = reduce(count_func, nums1)
        # 可以传初始值
        res2 = reduce(count_func, nums1, 100)
        print('res1:', res1)
        print('res2:', res2)
        
        print('------------示例2---------------')
        res3 = reduce(lambda a, b: a * b, nums1, 1)
        print('res3:', res3)
        
        print('------------示例3---------------')
        # 字符串拼接
        str_list1 = ['ab', 'cd', 'ef']
        res4 = reduce(lambda a, b: a + b, str_list1)
        print('res4:', res4)
      
    • 在这里插入图片描述

8 列表推导式

  • 用一条简洁语句,从可迭代对象中,生成新列表的语法结构。
  • 语法格式:[ 表达式 for 变量 in 可迭代对象 ]
  • 示例
    •   # 需求:让列表中每个元素,都变为原来的2倍,得到的是一个新的列表
      
        # 方式1:用map函数
        mums1 = [10, 20, 30, 40]
        res1 = list(map(lambda x: x * 2, mums1))
        print('res1:', res1)
        
        # 方式2:用 for循环 + append()
        nums2 = [10, 20, 30, 40]
        res2 = []
        for n in nums2:
            res2.append(n * 2)
        
        print('res2:', res2)
        
        # 方式3:用列表推导式
        nums3 = [10, 20, 30, 40]
        res3 = [n * 2 for n in nums3]
        print('res3:', res3)
        
        # 带条件的列表推导式-取出大于20的元素再乘以2形成一个新列表
        nums4 = [10, 20, 30, 40]
        res4 = [n * 2 for n in nums4 if n > 20]
        print('res4:', res4)
        
        # 字典推导式
        names = ['张三', '李四', '王五']
        scores = [85, 90, 82]
        res5 = {names[i]: scores[i] for i in range(len(names))}
        print('res5:', res5)
        
        # 集合推导式
        names2 = ['张三', '李四', '王五']
        res6={n + '!' for n in names2}
        print('res6:', res6)
        
        # Python中没有元组推导式
      
    • 在这里插入图片描述

9 常用内置函数

9.1 输入与输出

  • print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False):输出指定内容。参数含义如下
    • objects:要输出的内容
    • sep:输出多个内容时的分隔符
    • end:结尾追加的内容
    • file:输出位置(默认控制台)
    • flush:是否立即刷新输出
  • input():获取用户输入
  • 示例代码
    •   import time
      
        # 默认打印
        print(10, 20, 30, 40)
        
        # 指定分隔符
        print(10, 20, 30, 40, sep='-')
        
        # 指定结尾字符
        print(10, 20, 30, 40, sep='-', end='!')
        
        # 指定打印位置-打印到文件中
        f1 = open('a.txt', 'w', encoding='utf-8')
        print(10, 20, 30, 40, file=f1)
        
        # 进度条效果
        print('\n加载中', end='')
        for index in range(5):
            print('.', end='', flush=True)
            time.sleep(0.5)
        print('完成!', end='', flush=False)
        
        
        for index in range(1, 101):
            print(f'\r已加载{index}%', end='', flush=True)
            time.sleep(0.1)
      
    • 在这里插入图片描述

9.2 类型转换

  • int():转为整数
  • float():转为浮点数
  • str():转为字符串
  • bool():转为布尔值
  • list():转为列表
  • tuple():转为元组
  • set():转为集合
  • dict():转为字典

9.3 数学相关

  • abs():取绝对值
  • round():银行家舍入法:小于5舍,大于5入,等于5看奇偶(奇入偶舍)
  • pow(a, b):计算 a 的 b 次方
  • pow(a, b, c):计算 a 的 b 次方后,再对 c 取模
  • divmod(a, b):返回 (商, 余数)
  • max():最大值(支持 key)
  • min():最小值(支持 key)
  • sum():求和
  • map():加工一组数据
  • filter():按条件过滤数据
  • reduce():合并计算(需 functools.reduce)
  • sorted():排序(支持 key)
  • 示例代码
    •   # 取绝对值
        print('abs(-12):', abs(-12))
        
        # 四舍五入 - 大于5舍、小于5入、等于5看奇偶(奇入偶舍)
        print('round(3.14):', round(3.14))
        print('round(12.88):', round(12.88))
        print('round(4.5):', round(4.5))
        print('round(5.5):', round(5.5))
        print('round(3.1415926, 4):', round(3.1415926, 4)) # 保留四位小数
        
        # 幂运算
        print('pow(2, 3): ', pow(2, 3)) # 2的3次方
        print('pow(2, -1): ', pow(2, -1)) # 2的-1次方
        print('pow(2, 0.5): ', pow(2, 0.5)) # 2的开平方
        print('pow(2, 4, 5): ', pow(2, 4, 5)) # 2的4次方对5取模
        
        # 商和余数
        print('divmod(10, 3): ', divmod(10, 3)) # 10除以3的商和余数
      
    • 在这里插入图片描述

9.4 数据容器相关

  • len():获取容器中元素个数
  • range():生成数字序列(常用于循环)
  • enumerate():为序列添加索引
  • zip():将多个序列一一配对
  • 示例代码
    •   # range()
      
        for index in range(1, 10):
            print(index, end=' ')
        print('\n')
        
        # 设置步长
        for index in range(1, 10, 2):
            print(index, end=' ')
        print('\n')
        
        # zip()
        names = ('张三', '李四', '王五')
        scores = [80, 90, 88]
        res1 = zip(names, scores)
        print('res: ', list(res1))
      
    • 在这里插入图片描述

9.5 类型判断与对象相关

  • type():查看对象类型
  • isinstance(obj, type):判断对象是否属于某个类型
  • issubclass(A, B):判断类 A 是否为类 B 的子类
  • id():查看对象的内存地址

9.6 逻辑判断相关

  • all():所有元素为真时返回 True
  • any():至少一个为真返回 True
  • 示例代码
    •   l1 = [10, 'Python', {1, 2, 3}, 0]
        print(all(l1))
        print(any(l1))
      
    • 在这里插入图片描述

9.7 字符串赋值相关

  • ord():获取字符的 Unicode 编码值
  • chr():将 Unicode 编码值转换为字符

10 浅拷贝与深拷贝

10.1 直接赋值

  • 代码示例

    •   # 直接赋值
        # 两个变量会指向同一个对象,修改其中一个,会互相影响
        
        nums1 = [10, 20, 30, 40]
        nums2 = nums1
        print('nums1内存地址: ', hex(id(nums1)))
        print('nums2内存地址: ', hex(id(nums2)))
        print('修改前 nums1:', nums1)
        print('修改前 nums2:', nums2)
        nums2[3] = 99
        print('修改后 nums1:', nums1)
        print('修改后 nums2:', nums2)
      
      
    • 在这里插入图片描述
  • 图片示例

    • 在这里插入图片描述
    • 在这里插入图片描述
    • 在这里插入图片描述

10. 2 浅拷贝

  • 浅拷贝会创建一个新的外层容器,但内部的元素仍然引用原来的对象。

  • 示例代码

    •   import copy
        
        nums1 = [10, 20, 30, 40]
        # 对nums1进行浅拷贝
        nums2 = copy.copy(nums1)
        
        print('nums1内存地址: ', hex(id(nums1)))
        print('nums2内存地址: ', hex(id(nums2)))
        
        print('修改前 nums1:', nums1)
        print('修改前 nums2:', nums2)
        nums2[3] = 99
        print('修改后 nums1:', nums1)
        print('修改后 nums2:', nums2)
      
    • 在这里插入图片描述
  • 图片示例

    • 在这里插入图片描述
    • 在这里插入图片描述
  • 浅拷贝存在的问题:嵌套数据仍然是共享的,修改嵌套数据会互相影响。

  • 代码示例

    •   import copy
      
        nums1 = [10, 20, 30, [40, 50]]
        # 对nums1进行浅拷贝
        nums2 = copy.copy(nums1)
        
        print('nums1内存地址: ', hex(id(nums1)))
        print('nums2内存地址: ', hex(id(nums2)))
        
        print('修改前 nums1:', nums1)
        print('修改前 nums2:', nums2)
        nums2[3][0] = 99
        print('修改后 nums1:', nums1)
        print('修改后 nums2:', nums2)
      
    • 在这里插入图片描述
  • 图片示例

    • 在这里插入图片描述
    • 在这里插入图片描述
    • 在这里插入图片描述

10.3 深拷贝

  • 创建一个新的外层容器,同时对内部所有【可变对象】进行递归复制(不可变对象不复制,继续引用)。

  • 代码示例

    •   import copy
      
        nums1 = [10, 20, 30, [40, 50]]
        # 对nums1进行深拷贝
        nums2 = copy.deepcopy(nums1)
        
        print('nums1内存地址: ', hex(id(nums1)))
        print('nums2内存地址: ', hex(id(nums2)))
        
        print('修改前 nums1:', nums1)
        print('修改前 nums2:', nums2)
        nums2[3][0] = 99
        print('修改后 nums1:', nums1)
        print('修改后 nums2:', nums2)
      
    • 在这里插入图片描述
  • 图片示例

    • 在这里插入图片描述
    • 在这里插入图片描述
  • 深拷贝特点

    • 深拷贝可以彻底消除数据之间的相互影响
    • 深拷贝遇到不可变对象不会复制,会直接引用。
  • 注意点

    • 示例代码

      •   import copy
        
          # 深拷贝只复制可变对象,不可变对象会直接引用
          a = 666
          b = copy.deepcopy(a)
          
          print('hex(id(a)): ', hex(id(a)))
          print('hex(id(b)): ', hex(id(b)))
          
          # 元组中如果只包含不可变对象,则深拷贝没有效果
          nums1 = (10, 20, 30, (40, 50))
          nums2 = copy.deepcopy(nums1)
          print('hex(id(nums1)): ', hex(id(nums1)))
          print('hex(id(nums2)): ', hex(id(nums2)))
        
      • 在这里插入图片描述
    • 深拷贝只复制可变对象,不可变对象会直接引用。

    • 元组中如果只包含不可变对象,则深拷贝没有效果。

11 四种作用域

  • Python 中有四种作用域,分别是:

      1. Local(局部作用域)
      1. Enclosing(外层作用域)
      1. Global(全局作用域)
      1. Built-in(内建作用域)
  • 当访问一个变量时,Python 会按以下顺序查找: Local => Enclosing => Global => Built-in

  • 代码示例

    •   a = 100
      
        def test(b):
            print('我是test函数')
            print('test中打印的a是', a)
            print('test收到的参数b是', b)
            c = 200
            d = 300
            print('test中的c和d是', c, d)
            def inner():
                e = 400
                nonlocal c # 这样声明后,操作c作用的是外部作用域
                c = 999
                print('inner中的e是', e)
                print('inner中打印的c是', c)
            inner()
            print('test中的c和d是', c, d)
        
        
        print('全局打印的a是', a)
        
        test(66)
      
    • 在这里插入图片描述
  • 图片示例

    • 在这里插入图片描述

11.1 局部作用域(Local)

  • 函数的内部就是局部作用域,局部作用域中的变量,只在该函数内部可见。
  • 特点:
    • 每次调用函数都会创建一个新的局部作用域。
    • 函数运行结束后,局部作用域会随之销毁。
    • 局部作用域优先级最高,即:查找一个变量时,Python 会首先在局部作用域中查找。
  • 代码示例
    •   def test():
            x = 10  # x 在局部作用域
            print(x)  # 可以访问
        
        test()
        print(x)  # ❌ 报错:x 在局部之外不可见
      

11.2 外层作用域(Enclosing)

  • 如果函数中又定义了函数,那么外层函数的作用域,就是内层函数的 Enclosing 作用域。
  • 特点:
    • 只有当函数“嵌套定义”时才会出现。
    • 内层函数可以读取外层函数变量。
    • 想修改外层变量必须使用nonlocal。
  • 示例代码
    •   def outer():
            y = 20
            def inner():
                nonlocal y
                y = 99
                print('inner y: ', y)
        
            print('outer y: ', y)
            inner()
            print('outer y: ', y)
        
        outer()
      
    • 在这里插入图片描述

11.3 全局作用域(Global)

  • .py文件就是全局作用域,全局作用域中的变量,在当前.py文件的任何位置都可以访问。
  • 特点:
    • 全局变量只在当前.py文件中可见。
    • 函数内部可以使用global关键字修改全局变量。
  • 示例代码
    •   a = 100
      
        def test():
            global a
            a = 200  # 修改全局变量
        
        print(a)
        test()
        print(a)
      
    • 在这里插入图片描述

11.4 内建作用域(Built-in)

  • Python 预先定义好的东西,会放在内建作用域中,所有.py文件都可以直接使用。
  • 特点:
    • 所有.py文件都能直接使用其中的名称。
    • 例如:print、len、range、sum、max 等。
    • 查找优先级最低,即:查找一个变量时,内建作用域是“最后一道防线”。

12 闭包

12.1 前置知识

  • 前置知识 1: 局部作用域的生命周期。
    • 每次调用函数时,都会创建一个新的局部作用域。
    • 函数执行完毕后,该作用域就会被销毁,其中的局部变量,也会随之释放。
  • 前置知识 2:内层函数访问外层变量。
    • 【内层函数】可以访问到【外层函数】作用域中的变量。
    • 访问外层变量不用nonlocal,修改外层变量时要使用nonlocal。
  • 示例代码
    •   # 每次调用函数时,都会创建一个新的局部作用域。
        # 函数执行完毕后,该作用域就会被销毁,其中的局部变量,也会随之释放。
        def outer():
            num = 10
            num += 1
            print('outer() 中的num:', num)
        
        # 两次打印结果一样
        outer()
        outer()
        
        # 【内层函数】可以访问到【外层函数】作用域中的变量。
        # 访问外层变量不用nonlocal,修改外层变量时要使用nonlocal。
        
        def outer_2():
            num = 10
        
            def inner():
                nonlocal num
                num = 99
                print('inner() 中的num:', num)
            inner()
            print('outer_2() 中的num:', num)
        
        outer_2()
      
    • 在这里插入图片描述

12.2 闭包概念

  • 闭包 = 内层函数 + 被内层函数引用的外层变量。
  • 产生闭包的三个条件如下
      1. 必须有函数嵌套
      1. 内层函数使用了外层函数的变量
      1. 外层函数返回内层函数
  • 示例代码
    •   # 闭包 = 内层函数 + 被内层函数所引用的外层变量
        # 闭包产生的条件
        # 1. 要有函数嵌套
        # 2. 内层函数中使用了外层函数的变量
        # 3. 外层函数要返回内层函数 - 只有返回内层函数,闭包才能“活下来”
        
        def outer():
            num = 10
            score = 0.5
            msg = 'hello world'
        
            print('num 的地址:', hex(id(num)))
            print('score 的地址:', hex(id(score)))
        
            def inner():
                nonlocal num
                num += 1
                print('inner() 中的num:', num)
                print('inner() 中的score:', score)
        
            return inner
        
        f_inner = outer()
        
        # outer函数中,被inner所使用到的变量,会被封存到【闭包单元(cell)】中。
        # 这些cell会组成一个__closure__元组,最终放在了 inner 函数身上。
        print(f_inner.__closure__)
        
        f_inner()
        f_inner()
        f_inner()
        
        print('闭包单元中的num:', f_inner.__closure__[0].cell_contents)
        print('闭包单元中的score:', f_inner.__closure__[1].cell_contents)
      
    • 在这里插入图片描述

12.3 闭包注意点

  • 调用n次外层函数,会得到n个不同的闭包,并且这些闭包之间互不影响。
  • 内层函数中用到的外层变量是可变对象,多个闭包之间依然互不影响。
  • 示例代码
    •   # 调用n次外层函数,会得到n个不同的闭包,并且这些闭包之间互不影响
      
        print('**************示例1******************')
        def outer():
            num = 10
            print('num 的地址:', hex(id(num)))
        
            def inner():
                nonlocal num
                num += 1
                print('inner() 中的num:', num)
        
            return inner
        
        f1 = outer()
        print(f1.__closure__)
        f1()
        f1()
        f1()
        
        f2=outer()
        print(f1.__closure__)
        f2()
        f2()
        
        
        # 内层函数中用到的外层变量是可变对象,多个闭包之间依然互不影响
        print('**************示例2******************')
        def outer_2():
            nums = []
        
            def inner_2(value):
                nonlocal nums
                nums.append(value)
                print('inner_2() 中的nums:', nums)
            return inner_2
        
        f3 = outer_2()
        f3(10)
        f3(20)
        f3(30)
        
        f4 = outer_2()
        f4(40)
        f4(50)
      
    • 在这里插入图片描述

12.4 闭包优点

  • 可以“记住”状态:不用全局变量,也不用写类,就能在多次调用之间保存数据。
  • 可以做“配置过的函数”:先传一部分参数,把环境固定住,得到一个定制版函数。
  • 可以实现简单的“数据隐藏”:外层变量对外不可见,只能通过内层函数访问。
  • 是装饰器(decorator)等高级用法的基础。
  • 示例代码
    •   # 可以“记住”状态:不用全局变量,也不用写类,就能在多次调用之间保存数据。
      
      
        # 可以做“配置过的函数”:先传一部分参数,把环境固定住,得到一个定制版函数。
        def beauty(char, n):
            def show_msg(msg):
                print(char * n + msg + char * n)
            return show_msg
        
        show_1 = beauty('*', 3)
        show_1('hello')
        show_1('world')
        
        show_1 = beauty('&', 5)
        show_1('welcome')
        show_1('BeiJing')
        
        # 可以实现简单的“数据隐藏”:外层变量对外不可见,只能通过内层函数访问。
        
        
        # 是装饰器(decorator)等高级用法的基础。
      
    • 在这里插入图片描述

12.5 闭包缺点

  • 理解成本较高:对初学者不太友好,滥用会让代码难读。
  • 如果闭包里引用了很大的对象,又长期不释放,可能会增加内存占用。
  • 很多场景下,其实用【类 + 实例属性】会更清晰,闭包不一定是最优解。
  • 示例代码
    •   # 理解成本较高:对初学者不太友好,滥用会让代码难读。
        # 如果闭包里引用了很大的对象,又长期不释放,可能会增加内存占用。
        # 很多场景下,其实用【类 + 实例属性】会更清晰,闭包不一定是最优解。
        
        class Beauty:
            def __init__(self, char, n):
                self.char = char
                self.n = n
        
            def show_msg(self, msg):
                print(self.char * self.n + msg + self.char * self.n)
        
        b1 = Beauty('*', 4)
        b1.show_msg('hello')
        
        b2 = Beauty('$', 3)
        b2.show_msg('world')
      

13 装饰器

  • 概述:装饰器是一种在【不修改原函数代码】的前提下,对函数进行【增强】的工具。 它是 Python 中非常强大的语法特性,常用于:日志、校验、计时、缓存、权限控制等。
  • 核心语法: 装饰器是一种可调用对象(通常是函数),接收一个函数作为参数,并返回一个新函数。

13.1 函数装饰器

  • 定义装饰器核心规则:

    • 接收被装饰的函数、同时返回新函数(wrapper)。
    • 装饰器“吐出来”的是 wrapper 函数,以后别人调用的也是 wrapper 函数。
    • 为了保证参数的兼容性,wrapper 函数要通过 *args 和 **kwargs 接收参数。
    • wrapper 函数中主要做的是:调用原函数(被装饰的函数)、执行其它逻辑,但要记得将原函数的返回值 return 出去。
  • 应用:在不改变原函数的前提下,给函数统一加上:日志、计时、校验、缓存等功能。

  • 示例代码1:手动装饰

    •   def say_hello(func):
            def wrapper(*args, **kwargs):
                print('hello world')
                return func(*args, **kwargs)
            return wrapper
        
        
        def add(x, y):
            res = x + y
            print(f'{x} + {y} = {res}')
            return res
        
        # 正常调用add函数
        print('***********正常调用************')
        res1 = add(1, 2)
        print('res1:', res1)
        
        # 需求: 在不修改add函数的情况下,给add函数增加一些额外的功能
        # 实现方案: 使用装饰器
        print('***********使用装饰器(手动装饰)************')
        add = say_hello(add)  # 手动装饰(不推荐)
        res2 = add(1, 2)
        print('res2:', res2)
      
    • 在这里插入图片描述
  • 示例代码2:语法糖@

    •   def say_hello(func):
            def wrapper(*args, **kwargs):
                print('hello world')
                return func(*args, **kwargs)
            return wrapper
        
        @say_hello # 相当于add = say_hello(add)
        def add(x, y):
            res = x + y
            print(f'{x} + {y} = {res}')
            return res
        
        res1 = add(1, 2)
        print('res1:', res1)
      
    • 在这里插入图片描述
  • 示例代码3:带参数的函数装饰器

    •   # 带参数的装饰器
        # say_hello 接收配置
        # outer 接收函数
        # wrapper 接收参数
        def say_hello(msg):
            def outer(func):
                def wrapper(*args, **kwargs):
                    print(f'hello world, 我要开始{msg}计算')
                    return func(*args, **kwargs)
                return wrapper
            return outer
        
        
        @say_hello('加法')
        def add(x, y, z):
            res = x + y + z
            print(f'{x} + {y} + {z} = {res}')
            return res
        
        @say_hello('减法')
        def sub(x, y):
            res = x - y
            print(f'{x} - {y} = {res}')
            return res
        
        result1 = add(1, 2, 3)
        print('result1:', result1)
        
        result2 = sub(4, 5)
        print('result2:', result2)
      
    • 在这里插入图片描述
  • 示例代码4:多个函数装饰器一起使用

    •   # 多个装饰器一起使用
      
        def test1(func):
            print('我是test1装饰器')
            def wrapper(*args, **kwargs):
                print('test1前面追加的逻辑')
                res = func(*args, **kwargs)
                print('test1后面追加的逻辑')
                return res
            return wrapper
        
        def test2(func):
            print('我是test2装饰器')
            def wrapper(*args, **kwargs):
                print('test2前面追加的逻辑')
                res = func(*args, **kwargs)
                print('test2后面追加的逻辑')
                return res
            return wrapper
        
        # 等价于add=test1(test2(add))
        @test1
        @test2
        def add(x, y):
            result = x + y
            print(f'{x} + {y} = {result}')
            return result
        
        
        result1 = add(1, 2)
        print('result1:', result1)
      
    • 在这里插入图片描述

13.2 类装饰器

  • 包含__call__方法的类,就是类装饰器。
  • 像调用函数一样,去调用类装饰器的实例对象,就会触发__call__方法的调用。
  • __call__方法通常接收一个函数作为参数,并且会返回一个新函数。
  • 示例代码1:手动装饰
    •   # 包含__call__方法的类,就是类装饰器。
        # 像调用函数一样,去调用类装饰器的实例对象,就会触发__call__方法的调用。
        # __call__方法通常接收一个函数作为参数,并且会返回一个新函数。
        
        class SayHello:
            def __call__(self, func):
                def wrapper(*args, **kwargs):
                    print('hello world')
                    return func(*args, **kwargs)
                return wrapper
        
        def add(x, y):
            res = x + y
            print(f'{x} + {y} = {res}')
            return res
        
        # 正常调用add函数
        print('*********正常调用************')
        result1 = add(1, 2)
        print('result1:', result1)
        
        # 使用SayHello装饰add函数(手动装饰)
        print('*********手动装饰************')
        say1 = SayHello()
        add = say1(add)
        result2 = add(1, 2)
        print('result2:', result2)
      
    • 在这里插入图片描述
  • 示例代码2:语法糖@
    •   # 包含__call__方法的类,就是类装饰器。
        # 像调用函数一样,去调用类装饰器的实例对象,就会触发__call__方法的调用。
        # __call__方法通常接收一个函数作为参数,并且会返回一个新函数。
        
        class SayHello:
            def __call__(self, func):
                def wrapper(*args, **kwargs):
                    print('hello world')
                    return func(*args, **kwargs)
                return wrapper
        
        # 使用@语法糖使用类装饰器
        @SayHello()
        def add(x, y):
            res = x + y
            print(f'{x} + {y} = {res}')
            return res
        
        result2 = add(1, 2)
        print('result2:', result2)
      
    • 在这里插入图片描述
  • 示例代码3:带参数的类装饰器
    •   class SayHello:
            def __init__(self, msg):
                self.msg = msg
        
            def __call__(self, func):
                def wrapper(*args, **kwargs):
                    print(f'hello world, 我要开始{self.msg}')
                    return func(*args, **kwargs)
                return wrapper
        
        # 使用@语法糖使用类装饰器
        @SayHello('加法计算')
        def add(x, y):
            res = x + y
            print(f'{x} + {y} = {res}')
            return res
        
        result2 = add(1, 2)
        print('result2:', result2)
      
    • 在这里插入图片描述
  • 示例代码4:多个类装饰器一起使用
    •   class Test1:
            def __call__(self, func):
                def wrapper(*args, **kwargs):
                    print('Test1追加的逻辑')
                    return func(*args, **kwargs)
                return wrapper
        
        class Test2:
            def __call__(self, func):
                def wrapper(*args, **kwargs):
                    print('Test2追加的逻辑')
                    return func(*args, **kwargs)
                return wrapper
        
        @Test1()
        @Test2()
        def add(x, y):
            res = x + y
            print(f'{x} + {y} = {res}')
            return res
        
        result = add(1, 2)
        print('result:', result)
      
    • 在这里插入图片描述

14 类型注解

14.1 变量类型注解

  • 给变量加上类型说明,可增强代码的可读性,让IDE的提示更友好。
  • 示例代码
    •   # 变量类型注解: 给变量加上类型说明,可增强代码的可读性,让IDE的提示更友好。
      
        num: int = 100
        price: float = 12.5
        message: str = 'hello world'
        is_vip: bool = True
        result: None = None  # 语法没问题,但这样写没有意义
        
        # 注意:可以先写变量的类型注解,以后再赋值
        school: str
        school = '北京大学'
        
        # 容器类型注解
        # hobby1是列表,并且列表中的所有元素必须是 str 类型
        hobby1: list[str] = ['Python', 'Java', 'C++']
        
        # hobby2是列表,列表中的所有元素可以是 str 或 int 类型
        hobby2: list[str | int] = ['Python', 'Java', 100, 'C++', 520]
        
        # hobby3是列表,列表中的所有元素可以是 str 或 int 类型(旧写法)
        from typing import Union
        hobby3: list[Union[int, str]] = ['Python', 'Java', 'C++', 789]
        
        # city1是集合,并且集合中所有元素必须是str类型
        city1: set[str] = {'北京', '上海', '广州'}
        
        
        # city2是集合,集合中元素必须可以是str、float、bool类型
        city2: set[str | float | bool] = {'北京', True, 12.88}
        
        
        # person1是字典,键是str类型,值是int类型
        person1: dict[str, int] = {'张三': 18, '李四': 20, '王五': 16}
        
        # person2是字典,键是str或int类型,值是int类型
        person2: dict[str|int, int] = {'张三': 18, '李四': 20, '王五': 16, 12:22}
        
        # scores1是元组,并且元组中仅包含1个int类型的元素
        scores1: tuple[int] = (10,)
        
        # scores2是元组,并且元组中仅包含3个int类型的元素
        scores2: tuple[int, int, int] = (10, 20, 30)
        
        # scores3是元组,元组中可以包含任意个数的元素,但元素的类型必须是int
        scores3: tuple[int, ...] = (10, 20, 30, 40, 50, 60, 70, 80)
        
        # scores4是元组,元组中可以包含任意个数的元素,元素的类型可以是int或str
        scores4: tuple[int | str, ...] = (10, 20, 30, 'hello', 50, 'world', 70)
        
        # Python会根据初始赋值推导变量的类型
        # 对于普通变量:后续如果改变类型,不会警告
        # 对于容器变量:要求内部元素类型必须与推导出来的一致,否则就会警告
        x = 199
        print('x: ', x)
        x = 'hello world' # 不会警告
        
        y = [10, 20, 30]
        print('y: ', y)
        y.append('40')  # 会警告,但不会报错
        print('y: ', y)
      

14.2 函数类型注解

  • 函数类型注解:给函数的【参数】和【返回值】添加类型说明。
  • 语法格式:函数名(参数1: 类型, 参数2: 类型) -> 返回值类型:
  • 示例代码
    •   # 函数类型注解:给函数的【参数】和【返回值】添加类型说明。
        # 语法格式:函数名(参数1: 类型, 参数2: 类型) -> 返回值类型:
        
        # 示例1:设置参数类型注解、设置返回值类型注解
        def add1(x: int, y: int) -> int:
            return x + y
        
        res1 = add1(19, 29)
        print('res1: ', res1)
        
        # 示例2:参数有默认值(Python可以推导处参数的类型)
        def add2(x=1, y=2) -> int:
            return x + y
        res2 = add2(20, 40)
        print('res2: ', res2)
        
        # 示例3:设置多个返回值的类型注解
        def show_nums_info(nums: list[int]) -> tuple[int, int, float]:
            max_value = max(nums)
            min_value = min(nums)
            res = max_value / min_value
            return max_value, min_value, res
        
        res3 = show_nums_info([10, 20, 30, 40, 50, 60])
        print('res3: ', res3)
        
        
        # 示例4:设置*args的类型注解,要求args中每个参数都必须是int类型
        def add3(*args: int) -> int:
            return sum(args)
        
        res4 = add3(1, 2, 3, 6, 78, 22)
        print('res4: ', res4)
        
        # 示例5:设置**kwargs的类型注解,要求kwargs中每组参数的值,必须是str或int类型
        def show_info(**kwargs: str | int):
            print(kwargs)
        
        show_info(name='张三', age=23)
        
        # 获取函数的注解信息
        print(add1.__annotations__)
      
    • 在这里插入图片描述

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/new9232/article/details/167495588

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--