Python入门进阶教程:第9天——函数深入学习指南

你好,我是安然无虞。

文章目录

  • 再学函数
  • 1. 变量在函数中的作用域
  • 2. 函数的参数传递.
  • 补充学习: 不定长参数*args和**kwargs
  • 3. 值传递和引用传递
  • 补充学习: 把函数作为参数传递
  • 4. 匿名函数
  • 5. python中内置的常用函数
  • zip()
  • map()
  • filter()
  • all()
  • any()
  • 6. 函数练习

  • 再学函数

    1. 变量在函数中的作用域

    变量的作用域是指变量的作用范围.

    局部变量: 在函数体或局部范围内声明的变量称为局部变量.局部变量仅在局部作用域内有效.

    全局变量: 在函数之外或在全局范围内声明的变量称为全局变量.全局变量允许在函数内部和外部访问,但是不允许在函数内部修改.

    golbal: 用于在函数内部访问和修改全局作用域中的变量.通过在函数内部使用 global 关键字声明变量,该变量就可以在函数内部被修改,并且修改后的值也会影响到全局作用域中的变量.

    x = 10
    
    def modify_global_variable():
        global x
        x = 20
    
    print(x)
    modify_global_variable()
    print(x)
    

    nonlocal: 用于在嵌套函数中修改嵌套作用域外的变量。它允许你在内部函数中访问并修改外部函数的局部变量,而不是创建一个新的同名局部变量.并且会影响到外部函数中的同名变量.这样可以实现在嵌套函数中共享和修改变量的目的.(如果不加nonlocal, 也是可以在嵌套函数中访问到外层的变量的,只是无法修改)

     def outer():
         x = "local"
     
         def inner():
             nonlocal x
             x = "nonlocal"
             print(x)
     
         inner()
         print("outer:", x)
     
     x = 'global'
     outer()
     print('global:', x)
    

    2. 函数的参数传递.

    函数参数的传递可以分为4类:

    1. 位置参数: 调用函数时根据函数定义的参数的位置来传递参数.

    2. 关键字参数: 函数调用通过key=value的形式来传递参数, 可以让函数更加的清晰, 同时也不需要强调顺序.

    def self_introduction(name, age, gender):
        print(f"你好, 我的名字叫{name}")
        print(f"今年{age}岁, 性别{gender}")
    
    self_introduction(name="zhangsan", age=25, gender='男')
    

    需要注意的是: 位置参数和关键字参数可以混用, 但是位置参数必须在前面, 且需要匹配顺序.
    但是关键字参数之间, 不存在先后顺序.

    # 强制关键字参数.
    def self_introduction(name, age, *, gender):
       print(f"你好, 我的名字叫{name}")
       print(f"今年{age}岁, 性别{gender}")
      
       self_introduction("lisi", '男', gender=25)
    
    1. 缺省参数: 也叫做默认参数, 用于在定义函数的时候为参数提供默认值, 调用函数时可以不传递该参数的值, 所有的位置参数必须出现在默认参数前, 包括函数定义和调用.
    def self_introduction(name, age, come_from='中国'):
       print(f"你好, 我的名字叫{name}, 今年{age}岁, 我来自{come_from}")
       
       self_introduction(name="zhangsan", age=25)
    
    1. 不定长参数: 也叫做可变参数, 用于不确定调用的时候会传递多少参数.

      a. 位置传递: *args. 传递给函数的所有参数, 都会被args所接受,会根据传进去的参数的位置组成一个元组.

    2. *args允许函数接受任意数量的位置参数.
    3. 它以元组的形式传递参数,函数内部可以使用args变量来获取到参数.
    4. b. 关键字传递: **kwargs, 在参数是key=value的情况下, 所有的key-value都会被kwargs接收, 同时会将key-value组成一个字典.

    5. **kwargs允许函数接收任意数量的关键字参数.
    6. 它以字典的形式传递参数, 函数内部可以使用kwargs变量来引用这个字典.
    def average(a, b, c):
        print((a + b + c) / 3)
    

    1.通过位置传递不定常参数

    # 通过位置传递不定长参数
    
    def average(*args):
        # print(type(args)) -> 元组
        # print(args) -> (1,2,3,4,5,6)
        print(sum(args) / len(args))
    
    
    average(1, 2, 3, 4, 5 ,6)
    

    2.通过关键字传递不定长参数

    def self_introduction(**kwargs):
         print(kwargs)
         # print(f"你好, 我的名字叫{name}, 今年{age}岁, 我来自{come_from}")
         
         self_introduction(name="zhangsan", age=25, come_from='北京')
    

    3.不定长参数在Python当中的应用

    def self_introduction(*args, **kw):
        # print(args)
        # print(kw)
        # print(type(kwargs))
        # print(kwargs)
        print(f"你好, 我的名字叫{args[0]}, 今年{kw['age']}岁, 我来自{kw['come_from']}")
    
    self_introduction("zhangsan", age=25, come_from='北京')
    
    补充学习: 不定长参数*args和**kwargs

    请看这块代码:

    def wrapper(*args, **kwargs):
    	# 这里直接使用args和kwargs,并没有加上*号
        cache_key = (args, tuple(sorted(kwargs.items())))
        if cache_key in cache:
          return cache.get(cache_key)
          
        # 这里使用的是加上*号的*args和**args
        result = func(*args, **kwargs)
        cache[cache_key] = result
        return result
    

    注意我在上面代码中注释部分的内容, 现在请思考不加 * 号的不定长参数和加上 * 号的不定长参数在用法上有什么区别?

    首先再次说明一下Python当中的*args和**kwargs的用法:

    1. *args

    2. 作用:*args 用于将多个位置参数(非关键字参数)收集为一个元组
    3. 语法:在函数定义中,*args 放在形参列表的最后,表示接收所有未被其他形参捕获的位置参数
    4. **kwargs

    5. 作用:**kwargs 用于将多个关键字参数收集为一个字典
    6. 语法:在函数定义中,**kwargs 放在形参列表的最后,表示接收所有未被其他形参捕获的关键字参数
    7. 结合使用 *args 和 **kwargs
      在函数定义中,*args 和 **kwargs 可以同时使用,但 *args 必须在 **kwargs 之前

    在上面的代码中:

    def wrapper(*args, **kwargs):
    	# 这里直接使用args和kwargs,并没有加上*号
        cache_key = (args, tuple(sorted(kwargs.items())))
        if cache_key in cache:
          return cache.get(cache_key)
          
        # 这里使用的是加上*号的*args和**args
        result = func(*args, **kwargs)
        cache[cache_key] = result
        return result
    

    函数定义当中的形参: def wrapper(*args, **kwargs):

  • *args 和 **kwargs 的接收:
  • *args 接收所有位置参数, 存储为元组
  • **kwargs 接收所有关键字参数, 存储为字典
  • 下面在函数体中使用不定长参数:

    # 这里直接使用args和kwargs,并没有加上*号
    cache_key = (args, tuple(sorted(kwargs.items())))
    print(type(args)) # tuple
    print(type(kwargs)) # dict
    

    args 和 kwargs 是作为普通变量使用的,而不是作为参数传递给函数. 这里的 args 是一个元组,kwargs 是一个字典,它们已经被函数定义中的 *args 和 **kwargs 收集并存储了

    如果直接打印这里的args和kwargs的类型会发现分别是tuple()和dict()

    但是我们发现在函数调用中使用的是加上*号的不定长参数:

    # 这里使用的是加上*号的*args和**args
    result = func(*args, **kwargs)
    

    其实, 在函数调用中,*args 和 **kwargs 用于将已有的数据结构(元组或字典)**解包** 为函数的参数

  • *args:将一个元组解包为多个位置参数
  • **kwargs:将一个字典解包为多个关键字参数
  • 总结:

  • 函数定义中,*args 和 **kwargs 用于收集动态参数
  • 函数调用中,*args 和 **kwargs 用于解包已有的数据结构(元组或字典)为函数的参数
  • 3. 值传递和引用传递

    值传递(Pass by Value)和引用传递(Pass by Reference)是关于函数参数传递方式的两个概念.

    值传递是指将实际参数的值复制一份给形式参数,函数中对形式参数的修改不会影响到实际参数的值。在值传递中,函数中对形式参数的修改只会影响到函数内部,不会影响到函数外部。

    # 值传递
    def modify_value(x):
        x = 10
        print(x)
    
    value = 5
    modify_value(value)
    print(value)
    

    引用传递是指将实际参数的引用(地址)传递给形式参数,形式参数和实际参数指向同一块内存地址,函数中对形式参数的修改会影响到实际参数的值。在引用传递中,函数中对形式参数的修改会反映到函数外部.

    # 引用传递
    def modify_list(lst):
        lst.append(4)  # 修改参数(列表)的内容
        print(1, id(lst))
        # lst += [5] # 不会创建新对象, 所以id值不变, 注意这里不要误解+=
        lst = lst + [5] # 创建了新对象
        print(2, id(lst))
        print(1, lst)
    
    
    my_list = [1, 2, 3]
    print(3, id(my_list))
    modify_list(my_list)
    print(2, my_list)
    

    补充说明:

  • 不可变对象(如字符串、元组、数字等):修改内容时会创建新的对象,id 值会改变
  • 可变对象(如列表、字典、集合等):修改内容时不会创建新的对象,id 值保持不变
  • 需要注意的是,虽然在Python中没有严格的引用传递机制,但是对于可变对象(如列表、字典等),它们的传递方式是引用传递,因为它们的值可以在函数内部被修改. 而对于不可变对象(如数字、字符串等),它们的传递方式类是值传递,因为函数内部对形式参数的修改不会影响到实际参数

    理解值传递和引用传递的概念对于编写和调试代码非常重要,可以帮助我们更好地理解函数参数传递的机制和代码中的行为.

    # 注意比较下面两种调用方式:
    def f(x, li=[]):
        for i in range(x):
            li.append(i)
        print(li)
    
    # 1. 方式一
    print('当参数为4')
    f(4)
    print('当参数为5')
    # 注意哦, 这里使用的依然是之前的那个默认的li的地址
    f(5)
    
    # 2. 方式二
    print('当参数为4')
    f(4)
    print('当参数为5')
    f(5, li=[4, 5]) # 这里使用的是指定的列表, 所以地址变了
    
    补充学习: 把函数作为参数传递
    # 当把函数作为参数传递.
    a_list = [15, 111, 2232, 123123, 324234]
    
    # 将a_List排序, 排序的根据是数字的个位.
    def sorted_by(num):
        num_string = str(num)[-1]
        return int(num_string)
    
    b_list = sorted(a_list, key=sorted_by)
    print(b_list)
    
    
    # 改写之前的计算器:
    
    def add(a, b):
    	return a + b
    
    def calc(a, b, func):
      return func(a, b)
    
    res = calc(6, 6, add)
    print(res)
    

    4. 匿名函数

    也称为lambda函数,是一种简洁的函数定义方式,用于创建一次性的、简单的函数.

    匿名函数的语法形式为:lambda 参数: 表达式

    匿名函数的返回值就是 表达式的结果

    它由关键字 lambda 开头,后跟一个或多个参数,然后是冒号 :,最后是一个表达式, 匿名函数执行表达式的计算,并返回结果.

    匿名函数通常用于需要简单函数逻辑的地方,尤其是在需要传递函数作为参数的情况下,可以更简洁地定义函数,避免定义命名函数的繁琐。但需要注意的是,匿名函数通常用于表达式较短、逻辑简单的情况,对于复杂的函数逻辑,仍然建议使用命名函数进行定义

    def add(a, b):
      return a + b
    lambda a, b: a + b
    

    案例: numbers = [5, 2, 7, 1, 9] 对此列表排序, 根据对3取余的 结果进行排序.

    # numbers = [5, 2, 7, 1, 9] 对此列表排序, 根据对3取余的 结果进行排序.
    #            2  2  1  1  0
    numbers = [5, 2, 7, 1, 9]
    
    a_list = sorted(numbers, key=lambda x: x % 3)
    print(a_list)
    

    改写前面的计算器程序:

    def calculator(a, b, operator):
        operator_dict = {
            '+': lambda x, y: x + y,
            '-': lambda x, y: x - y,
            '*': lambda x, y: x * y,
            '/': lambda x, y: x / y,
        }
        return operator_dict.get(operator)(a, b)
    
    
    res = calculator(6, 6, '*')
    print(res)
    

    5. python中内置的常用函数

    zip()

    zip: zip([iterable, …]), 将多个可迭代对象打包成元组.它返回一个可迭代的对象,该对象生成元组,每个元组包含来自每个可迭代对象的元素, 如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同.以长的为主使用zip_longest方法

    # zip
    from itertools import zip_longest
    numbers = [1, 2, 3, 4]
    letters = ['a', 'b', 'c']
    x_list = ['d', 'e']
    
    zipped = list(zip_longest(numbers, letters, x_list, fillvalue=0))
    print(zipped)
    
    for item in zipped:
        print(item)
    
    map()

    map(): map(function, iterable):对可迭代对象中的每个元素应用指定的函数.

    # map
    def square(x):
        return x ** 2
    
    numbers = [1, 2, 3, 4, 5]
    
    # 要将numbers中的数字全部转换为字符串.
    a_list = list(map(str, numbers))
    print(a_list)
    squared_numbers = map(lambda x: x**2, numbers)
    print(squared_numbers)
    print(list(squared_numbers))
    
    filter()

    filter(): filter(function, iterable): 对可迭代对象中的元素进行过滤. 返回值为True时保存

    # filter
    def is_even(x):
        return x % 2 == 0
    
    numbers = [1, 2, 3, 4, 5]
    
    even_numbers = filter(lambda x: x % 2 != 0, numbers)
    
    print(list(even_numbers)) # [1, 3, 5]
    
    all()

    all(): all(iterable):判断可迭代对象中的所有元素是否都为真.如果是返回 True,否则返回 False.

    # all 判断可迭代对象中的所有元素是否都为真.如果是返回 True,否则返回 False.
    print(all(['a', 'b', 'c', ''])) # False
    print(all([])) # True 注意哦,Python规定如果可迭代对象为空, all()的结果就是 True
    
    numbers = [1, 2, 3, 4, 5, -1]
    # 使用 all() 函数判断是否所有元素都大于0
    sign = all(num > 0 for num in numbers)
    print(sign) # False
    
    any()

    any(): any(iterable): 判断可迭代对象中的任何一个元素是否为真(有一个为真就是真).不是则返回 False,如果有一个为 True,则返回 True.

    # any
    # print(any(['', 0, '', None]))
    # print(any([]))
    numbers = [1, 3, 5, -1]
    # 使用 any() 函数判断numbers中是否存在偶数元素
    sign = any(num % 2 == 0 for num in numbers)
    print(sign)
    

    6. 函数练习

    1.编写一个函数 get_sum_of_lists,接受多个列表作为参数,并返回它们对应位置元素的和的列表 短列表缺少的用0补齐.

    from itertools import zip_longest
    # [1, 2, 3, 4, 5, 6]
    # [6, 5, 4, 3, 2, 1]
    # 拆包
    def get_sum_of_lists(*args):
        # print(*args)
        zipped = list(zip_longest(*args, fillvalue=0))
        result = list(map(sum, zipped))
        return result
    
    
    lst1 = [1, 2, 3, 4, 5, 6, 7, 8]
    lst2 = [6, 5, 4, 3, 2, 1]
    lst3 = [6, 5, 4, 3, 2, 1, 3, 6]
    
    res = get_sum_of_lists(lst1, lst2, lst3)
    print(res)
    

    2.实现一个函数 get_max_length(words),接收一个字符串列表 words,返回列表中最长的单词的长度.

    例如,对于输入列表 [‘apple’, ‘banana’, ‘orange’, ‘watermelon’],函数应该返回 10,因为最长的单词是 ‘watermelon’,它的长度为 10

    def get_max_length(words):
        # len_list = list(map(len, words))
        len_list = [len(word) for word in words]
        return max(len_list)
    
    lst = ['apple', 'banana', 'orange', 'watermelon']
    print(get_max_length(lst))
    
    

    3.实现一个函数get_primes, 接收一个参数n, 函数功能是: 返回n以内(包含n)的所有的质数. get_primes函数体内使用lambda实现.

    只能被1跟它本身整除的数. 2, 3, 4, 5 ,6 ,7 ,8 ,9, 10 每一次取余的结果都不是0, 那么它就是质数. 反之, 就不是质数.

    # all, all true , false
    # filter
    # 5 2 3 4
    # 6 2 3 4 5
    def get_primes(n):
        primes_list = list(filter(lambda x: all(x % i != 0 for i in range(2, x)), range(2, n + 1)))
        return primes_list
    
    primes = get_primes(20)
    print(primes)
    

    遇见安然遇见你,不负代码不负卿。

    谢谢老铁的时间,咱们下篇再见~

    作者:安然无虞

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python入门进阶教程:第9天——函数深入学习指南

    发表回复