31天Python入门——第9天:再学函数

发布于:2025-03-23 ⋅ 阅读:(18) ⋅ 点赞:(0)

在这里插入图片描述

你好,我是安然无虞。


在这里插入图片描述

再学函数

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所接受,会根据传进去的参数的位置组成一个元组.

    • *args允许函数接受任意数量的位置参数.
    • 它以元组的形式传递参数,函数内部可以使用args变量来获取到参数.

    b. 关键字传递: **kwargs, 在参数是key=value的情况下, 所有的key-value都会被kwargs接收, 同时会将key-value组成一个字典.

    • **kwargs允许函数接收任意数量的关键字参数.
    • 它以字典的形式传递参数, 函数内部可以使用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

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

    • 作用:**kwargs 用于将多个关键字参数收集为一个字典
    • 语法:在函数定义中,**kwargs 放在形参列表的最后,表示接收所有未被其他形参捕获的关键字参数
  3. 结合使用 *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)
遇见安然遇见你,不负代码不负卿。
谢谢老铁的时间,咱们下篇再见~