Python侧开28期-偕行-学习笔记-python函数

函数

一、函数返回值return

1、return语句

  • 默认返回None:如果函数不适用return语句,默认会有返回值None;
  • 结束函数:当函数内部使用return语句后,函数执行到return后不会继续往下执行;
  • 允许多个return:在一个函数中,允许有多个return语句,可以实现在不同的条件下,返回不同的值,但同一时刻,只能有一个return语句被执行,因为只要执行return后函数就结束了。
  • 只能返回“一个值”:如果return后面只有一个值的时候很好理解,那就返回的确实只有一个值;那如果return后面写了多个值这句话应该怎么理解呢?从表面上看貌似返回了多个值,其实在内部:当return后面给多个值的时候,Python内部会将多个值赋值给一个元组对象,这个元组对象就是“一个值”的意思,这是Python的组包操作,在调用该函数使用返回值的时候,使用的其实是这个元组,只是元组内部有多个值,表面上看起来像是返回了多个值而已;
def getTwoNum():
    a = int(input("请输入第一个数字:"))
    b = int(input("请输入第二个数字:"))
    return a, b

result = getTwoNum()
print(result)
print(type(result))
------------------------------------------------------
请输入第一个数字:4
请输入第二个数字:9
(4, 9)
<class 'tuple'>

2、组包与解包

# 组包
nums = 1,2,3,4,5
print(nums)# (1, 2, 3, 4, 5)
print(type(nums))# <class 'tuple'>
print(nums[2])# 3

# 解包
a,b,c,d,e = nums
print(a,b,c,d,e)# 1 2 3 4 5

结论:通过代码可以发现,当将多个值,同时赋给一个变量时Python会进行自动组包操作将所有的数字组合成一个元组,再将元组赋值给变量。 当使用一个元组为多个变量进行赋值时,Python会将元组中的元素值,依次赋值给变量,这称为解包操作

二、参数处理

1、位置参数

  • 位置参数也称为必备参数,必须按照正确的顺序传到函数中,即调用时的数量和位置必须 和定义时是一样的。位置参数是会按照顺序进行参数的替换的。
    • 数量必须与定义时一致,在调用函数时,指定的实际参数的数量必须与形式参数的数量一致,否则将抛出 TypeError 异常,提示缺少必要的位置参数(missing x required positional argument)。
    • 位置必须与定义时一致,在调用函数时,指定的实际参数的位置必须与形式参数的位置一致,否则将抛出 TypeError 异常或者结果与预期不符的情况,例如入参的位置 1 需要一个 int 类型参数,而入参 2 位置需要一个 str 类型的参数,如果传递的位置不正确,那么 str 类型数据传递进去之后会报类型错误的异常;
def printMsg(n, msg):
    for i in range(n):
        print(f'第{i+1}次输出{msg}')

# 正确使用位置参数
printMsg(5, "Hogworts")
# 错误使用位置参数
printMsg("Hogworts", 5)# TypeError: 'str' object cannot be interpreted as an integer

2、关键字参数

  • 关键字参数是指使用形式参数的名字来确定输入的参数值,通过该方式指定实际参数时,不再需要与形式参数的位置完全一致,只要将参数名写正确即可,这样可以避免用户需要牢记参数位置的麻烦,使得函数的调用和参数传递更加灵活方便。
def printMsg(n, msg):
    for i in range(n):
        print(f'第{i+1}次输出{msg}')

# 关键字参数
printMsg(n=5, msg="Hogworts")
printMsg(msg="Hogworts", n=5)

3、默认参数

  • 在定义函数时,形参可以定义变量一样进行赋值,这个值就是默认该参数的默认值,调用函数时,如果指定了该参数的数据,则使用指定的数据,如果没有指定该参数的数据,则使用默认值的数据
    • 注意
      • 1、默认参数可以有多个;
      • 2、调用函数的时候可以使用位置参数(顺序)进行传递,也可以使用关键字参数(参数名赋值)进行传递;
      • 3、指定默认值的形式参数必须放在所有未指定默认值参数的后面,否则会产生语法错误;
def my_power(m, n=2,a=1):
    return m ** n + a

# 使用指定的参数--通过位置进行传递
print(my_power(2, 3,2))# 10
# 使用指定的参数--通过参关键字传递
print(my_power(n=2, a=3,m=2))# 7
# 使用默认值参数
print(my_power(2))# 5
# 使用默认参数n,a实际传值
print(my_power(2,a=2))# 6
# n实际传值,使用默认参数a
print(my_power(2,n=3))# 9
# 特别注意:指定默认值的形式参数必须放在所有未指定默认值参数的后面,否则会产生语法错误
def show(a, b=2, c):
    print(a, b, c)
------------------------------------------------------
    def show(a, b=2, c):
                     ^
SyntaxError: non-default argument follows default argument

4、可变参数

  • Python中提供了可变参数的概念用来解决参数个数不确定的的情况。

(1) 可变位置参数

  • Python使用*args参数做为形参,接收不确定个数的位置参数。
  • *args将接收到的任意多个实际参数放到一个元组中元组变量名为args。传入函数内部之后:
    • *args:元组内部的值,没有(),类似:1 2 3 。。。;
    • args:完整的元组数据,类似:(1,2,3…);
    • 也就是:传递给函数之后,*args和传递的内容一样,而args是给传递的值套了个“()”将其转成元组类型;
def my_sum(*args):
    print(args) # 完整的元组数据,类似:(1,2,3...)
    print(*args) # 元组内部的值,没有(),类似:1 2 3 。。。
    print(type(args)) # <class 'tuple'>
    # print(type(*args)) # 报错:TypeError: type.__new__() argument 1 must be str, not int
     s = 0
    for i in args:
        s += i

    print(s)
    print("*" * 10)

my_sum(1,2,3)
my_sum((1,2,3,4))
--------------------------------------------------------------------------------------
(1, 2, 3)
1 2 3
<class 'tuple'>
6
**********
((1, 2, 3, 4),)
(1, 2, 3, 4)
<class 'tuple'>
第二个调用运行部分报错,这个不重要,重要的是查看传参形式和传入之后如何使用!!!

(2)可变关键字参数

  • Python使用**kwargs参数做为形参,接收不确定个数的关键字参数。
  • **kwargs将接收到的任意多个实际参数放到一个字典中字典变量名为kwargs
def print_info(**kwargs):
    print(kwargs)# {'name': 'tom', 'age': 22, 'gender': 'male'}
    # print(**kwargs)# TypeError: 'name' is an invalid keyword argument for print()
    print(type(kwargs))# <class 'dict'>
    # print(type(**kwargs))# TypeError: type() takes 1 or 3 arguments
    for k,v in kwargs.items():
        print(k, v)
    print("*" * 10)

print_info(name="tom",age=22,gender="male")
-------------------------------------------------------------------------------------------
{'name': 'tom', 'age': 22, 'gender': 'male'}
<class 'dict'>
name tom
age 22
gender male
**********

Process finished with exit code 0

5、混合参数

  • 当定义函数时,参数列表中出现了多种类型的参数,定义时需要注意参数的定义顺序,如果顺序使用不正确,在调用函数时,可能会报错。
  • 正确顺序的定义为位置参数,可变位置参数,默认值参数,可变关键字参数
def info(name1, name2, *args, age1=18, age2=21, **kwargs):
    print("位置参数:")
    print("name1:", name1)
    print("name2:", name2)
    print("可变位置参数:")
    print("args:", args)
    print("默认参数:")
    print("age1:", age1)
    print("age2:", age2)
    print("可变关键字参数:")
    print("kwargs:", kwargs)

info("Alice", "Bob", "Charlie", "Dave", age2=30, occupation="Engineer", city="New York")
-------------------------------------------------------------------
位置参数:
name1: Alice
name2: Bob
可变位置参数:
args: ('Charlie', 'Dave')
默认参数:
age1: 18
age2: 30
可变关键字参数:
kwargs: {'occupation': 'Engineer', 'city': 'New York'}
  • 一般函数在不确定参数的情况下,会将上面的形式简化定义,用来接收任意数量的参数。 使用 *args 接收所有的位置参数,使用 **kwargs 接收所有的关键字参数。
def funcname(*args, **kwargs):
    pass

  • 特别注意:传递参数时,需要先传递完所有的位置参数后,才能传递关键字参数。

三、变量作用域

  • 作用域就是一个 Python 程序可以直接访问命名空间的正文区域。
  • 在一个 Python 程序中,直接访问一个变量,会从内到外依次访问所有的作用域直到找到,否则会报未定义的错误。
  • Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。
  • 变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。Python 的作用域一共有4种,分别是:
    • L(Local):最内层,包含局部变量,比如一个函数/方法内部。
    • E(Enclosing):包含了非局部(non-local)也非全局(non-global)的变量,一般在闭包中出现。
    • G(Global):当前脚本的最外层,比如当前模块的全局变量。
    • B(Built-in): 包含了内置(内建)的变量/关键字等,最后被搜索。
    • 查找规则顺序: L –> E –> G –> B 。在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内置中找。

1、局部变量

  • 局部变量是指在函数内部定义并使用的变量,它只在函数内部有效,即函数内部的名字只在函数运行时才会创建,在函数运行之前或者运行完毕之后,所有的名字就被销毁都不存在了。 所以,如果在函数外部使用函数内部定义的变量,就会抛出 NameError 异常。

2、全局变量

  • 定义在函数外的变量为全局变量,全局变量可以在当前程序文件的任何位置进行访问
  • 当全局变量和局部变量同名时根据作用域的查找规则顺序内部变量会屏蔽全局变量
# 局部变量与全局变量同名,会根据查找顺序:局部→局部外的具备(比如闭包)→全局变量→内置变量,所以在函数内部找到了就不会去全局找,相当于此时屏蔽了全局变量
# 全局变量定义
m = 10
def show():
    # 局部变量与全局变量同名
    m = "ABC"
    # 使用局部变量
    print("show:", m)

# 使用全局变量
print(m)
show()
# 使用全局变量
print(m)
-------------------------------------------------
10
show: ABC
10
  • 当全局变量和局部变量同名时如果想在函数内部修改全局变量需要使用global关键字对变量进行声明;
# 全局变量定义
# m = 10
# def show():
#     # 局部变量与全局变量同名
#     m = "ABC"
#     # 使用局部变量
#     print("show:", m)
# 
# # 使用全局变量
# print(m)
# show()
# # 使用全局变量--此时的全局变量已被重新赋值进行了修改
# print(m)
--------------------------------------------------------------
10
show: ABC
ABC
  • 全局变量主要有两种情况:
    • 如果一个变量在函数体外定义,那么不仅在函数外可以访问到,在函数内也可以访问到。在函数体以外定义的变量是全局变量。
msg = "学习测试开发来霍格沃兹测试学社" 
def var_demo():
    print("函数体内全局变量 msg 的值为", msg) 

var_demo()  
print("函数体外全局变量 msg 的值为", msg)
---------------------------------------------------------------
函数体内全局变量 msg 的值为 学习测试开发来霍格沃兹测试学社
函数体外全局变量 msg 的值为 学习测试开发来霍格沃兹测试学社
  • 情况二:如果一个变量在函数体内定义,并且使用 global 关键字修饰后,该变量也就变为全局变量。在函数体外可以访问该变量,并且在函数体内还可以对其进行修改
msg = "学习测试开发来霍格沃兹测试学社" # 创建一个局部变量并赋值
print("函数体外全局变量 msg 的值为", msg) # 在函数体外输出局部变量的值
def var_demo():
    global msg
    msg = "霍格沃兹测试学社值得信赖" # 此时已经申明了 msg 为全局变量
    print("函数体内变量 msg 的值为", msg) # 输出局部变量 msg 的值

var_demo()   # 调用函数
print("函数体外全局变量 msg 的值为", msg) # 在函数体外输出局部变量的值
-------------------------------------------------------------------------------------------------------
函数体外全局变量 msg 的值为 学习测试开发来霍格沃兹测试学社
函数体内变量 msg 的值为 霍格沃兹测试学社值得信赖
函数体外全局变量 msg 的值为 霍格沃兹测试学社值得信赖

3、 全局变量和局部变量的优缺点

  • 全局变量的优点:

      1. 全局变量在整个程序中都可访问,方便在不同的函数或模块之间共享数据
      1. 全局变量可以保存程序运行期间的状态,比如计数器或状态标志。
  • 全局变量的缺点:

      1. 全局变量的作用范围很大,容易被误修改,导致程序出现错误。
      1. 在多个函数中使用同名的全局变量时,很容易出现混淆和命名冲突的问题。
      1. 全局变量的使用不宜过多,因为它们会占用内存资源。如果程序中有太多的全局变量,会增加维护和调试的难度。
  • 局部变量的优点:

      1. 局部变量的作用范围限于所在的函数或代码块中,不会对其他函数或模块造成影响。这样可以防止变量的误修改或命名冲突。
      1. 局部变量仅在需要时才会被创建和销毁,节省了内存资源
  • 局部变量的缺点:

      1. 局部变量的作用范围有限,不能在其定义范围外直接访问。如果需要在多个函数之间共享数据,就不能使用局部变量,需要采用其他的方式来传递数据。
      1. 当函数中定义了较多的局部变量时,可能会使代码显得冗长和难以阅读,增加了编写和维护的复杂性。

四、匿名函数

1、匿名函数定义

  • 匿名函数是指没有名字的函数,应用在需要一个函数,但是又不想费神去命名这个函数的场合。在通常情况下,这样的函数只使用一次。在 Python 中使用 lambda 表达式创建匿名函数。
  • Lambda 函数可用于任何需要函数对象的地方
  • 在语法上,匿名函数只能是单个表达式
  • 在语义上,它只是常规函数定义的语法糖。
  • lambda 表达式中不能使用 if,for,while, return等语句, 但可以使用三目运算符

2、lambda表达式

  • 语法:result = lambda [arg1 [, arg2, .... , argn]]: expression
    • result:用于保存 lambda 表达式的引用;
    • [arg1 [, arg2, … , argn]]:可选,指定要传递的参数列表,多个参数间使用英文的逗号进行分隔。
    • expression:必选,指定一个实现具体功能的表达式。如果有参数,那么在该表达式中将应用这些参数。
def add(n1, n2):
    return n1 + n2

result = add(1,2)
print(result)


add = lambda x,y: x+y
result = add(2,3)
print(result)
print(type(add))# <class 'function'>

func = lambda x: x**2  if x > 3 else x**3
print(func(3))# 27
print(func(4))# 16

result = list(filter(lambda x: x%2 == 0),range(10))
print(result)# [0,2,4,6,8]

3、补充:三目运算符

  • 三目运算符也称为三元运算符,是指运算符在使用时,需要有三个操作数参与计算。
  • 可以将 Python 中的三目运算符理解成是 if-else 分支语句的简化单行模式。
  • 语法格式: [on_true] if [expression] else [on_false]
    • on_true: 条件为真时的结果;
    • on_false: 条件为假时的结果;
    • expression: 判断条件;
  • Python 会先判断 expression 条件表达式的结果,如果条件为真,则结果为 [on_true] ,条件为假,则结果为 [on_false]

4、匿名函数使用场景

  • 需要一个函数,但是又不想费神去命名这个函数;
  • 通常在这个函数只使用一次的场景下;
  • 可以指定短小的回调函数;
# 待排序的数据
students = [
    {'name': 'Alice', 'id': '1001', 'class': 'Class1'},
    {'name': 'Eve', 'id': '1005', 'class': 'Class2'},
    {'name': 'Charlie', 'id': '1003', 'class': 'Class1'},
    {'name': 'David', 'id': '1004', 'class': 'Class2'},
    {'name': 'Bob', 'id': '1002', 'class': 'Class1'},
    {'name': 'Frank', 'id': '1006', 'class': 'Class2'}
]

# students.sort()# TypeError: '<' not supported between instances of 'dict' and 'dict'

# 以名字排序
students.sort(key=lambda stu: stu["name"])
for s in students:
    print(s)

# # 以ID降序排序
students.sort(key=lambda stu: stu["id"],reverse=True)
for s in students:
    print(s)

五、递归算法

1、递归的定义

  • 当涉及解决问题时,递归是一种非常有用的编程技巧,为它设计一个出口,它允许函数调用自身以解决更小规模的问题,直到达到基本情况为止。

2、递归的基本原则

  • 定义基本情况:确定一个或多个输入的特殊情况,当满足这些条件时,递归函数将直接返回结果而不再调用自身。
  • 减小问题规模:通过调用自身来解决一个规模更小的问题,这样每次递归调用都在问题规模上取得了进展。也就是需要一个已定义好的规则来使其它非基本的情况转化为基本情况。
  • 终止条件:递归函数必须包含能够导致函数不再递归调用的条件,以避免无限递归。

3、 递归使用举例-阶乘

一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且0的阶乘为1。自然数n的阶乘写作n!。 对阶乘的定义进行分析:

  • 首先定义 f(n) 为阶乘函数。它的结果就是阶乘计算之后的值。
  • 从定义中能得到基本情况为: f(0) = 1, f(1) = 1
  • 其它情况: f(2) = 2*1 = 2*f(1),f(3) = 3*2*1 = 3*f(2)f(n) = f(n-1) * n
def f(n):
    """
    实现计算 n 的阶乘
    return:n 阶乘计算之后的值
    """
    if n == 0 or n == 1:
        # 对应基本情况
        return 1
    return f(n - 1) * n  # 对应递归情况

注意:

  • 大多数情况下的递归操作都可以使用循环所替代。

  • 在使用递归时,要注意避免陷入无限调用而产生的内存溢出。

练习一:斐波那契数列

"""
作业要求
编写一个Python程序,使用递归算法,生成并输出斐波那契数列的前n项,其中n是用户指定的正整数。
斐波那契数列,又称黄金分割数列,指的是:1、1、2、3、5、8、13、21、34....从第三个数开始,每个数字都是前两个数字之和。

分析:f(1)=1,f(2)=1,f(3)=f(2)+f(1),公式:f(n)=f(n-1)+f(n-2)
"""
def fblq(n):
    if n == 1 or n == 2:
        return 1
    else:
        return fblq(n-1)+fblq(n-2)
# 10个以内斐波那契数列
for i in range(1,11):
    print(fblq(i),end=" ")# 1 1 2 3 5 8 13 21 34 55