函数返回值与参数处理
函数返回值与参数处理
简介
在前面讲解了函数的基本定义和使用,通过一段时间的使用,对函数的使用有了一定的了解。
但是实际函数使用时,基本的定义和调用还远远没有达到函数的使用要求。
函数的主要作用是实现模块化代码,减少代码在程序中的冗余,提高代码的复用率。
函数通过接收一定的数据,根据业务逻辑进行相应的处理,然后将处理结果返回给调用者。
函数思想
实现函数要基于高内聚,低耦合的思想。
函数高内聚
高内聚低耦合指的是函数内部的代码要有高内聚性,而函数之间要保持低耦合性。
高内聚性指的是函数内部的代码逻辑和功能彼此相关,实现的是一个具体的功能或目标,各个部分之间密切配合。
一个高内聚的函数通常只完成一个明确的任务,并且其内部的代码逻辑清晰、简洁。
函数低耦合
低耦合性指的是函数之间的依赖关系较弱,相对独立,一个函数的改动不会对其他函数造成过多的影响。
低耦合的函数在设计上应该尽量减少使用全局变量或直接修改其他函数的参数,而是通过参数传递和返回值来进行数据交互。
高内聚低耦合优点
通过遵循函数的高内聚低耦合原则,可以带来以下优点:
-
可维护性:函数内部的代码高度相关和一致,使得函数更易于理解、调试和修改,减少了不相关的代码耦合。
-
可测试性:函数的高内聚性使得它们更易于独立地进行单元测试,因为它们具有清晰的功能和输入输出。
-
可复用性:函数之间的低耦合性使得它们可以在不同的上下文中被重复使用,提高了代码的复用性。
-
灵活性:函数之间相互独立且功能明确,使得系统更容易进行扩展和修改,不会对其他函数产生很大的影响。
在设计函数时,应该关注其功能的内聚性,确保函数只完成一个特定的任务,并且将其功能按照模块化的方式进行拆分。同时,尽量减少函数之间的直接依赖和共享状态,通过参数传递和返回值来进行数据交互,以减少函数之间的耦合度。
通过保持函数的高内聚低耦合,能够提高代码的可维护性、可测试性和可复用性,同时使得系统更具灵活性和可扩展性。
函数返回值
在设计一个函数时,可以通过函数的返回值,将函数功能的处理结果,返回给调用者。
而一个真正完整的函数,需要返回值将结果返回给调用者,而不是直接通过 print
函数将其输出。
比如,存在一个可以查找列表中最大数值的函数,而刚好调用者有需求在一个列表中要找到最大值,并且进行后续操作。
如果此时查找最大值函数没有返回结果而是直接输出的话,对于调用者来说,该函数没有任何意义。
Python 中使用 return
返回函数的结果,同时,return
也具有结束函数的作用。
return
结束函数执行
def show():
print("循环前输出内容")
for i in range(10):
print(i)
if i == 2:
return
print("循环后输出内容")
print("函数调用前输出内容")
show()
print("函数调用后输出内容")
上面的代码中,在循环变量 i
值为 2
时,执行了 return
,此时函数会立即结束,返回到函数调用语句处继续向后继续执行。
当通过 return
结束函数执行时,实际默认返回了一个 None
值。
函数只能返回一个值
一个函数只能返回一个结果值,当需要返回多个值时,需要进行组包处理。
def getString():
s = input("请输入一个字符串:")
return s
result = getString()
print(result.upper())
此函数用来从键盘获取一个字符串,并返回给了调用者。调用者得到结果后进行了大写处理。
如果有两个或更多的值想返回,都写在 return
后行不行呢?
def getTwoNum():
a = int(input("请输入第一个数字:"))
b = int(input("请输入第二个数字:"))
return a, b
m, n = getTwoNum()
print(m, n)
通过代码发现,函数执行结果和前面介绍的理论并不符合,但实际上,该函数返回的依然是一个值,只是 Python 在执行时,自动做了组包和解包操作,将多个值默认包装成一个元组返回。
def getTwoNum():
a = int(input("请输入第一个数字:"))
b = int(input("请输入第二个数字:"))
return a, b
result = getTwoNum()
print(result)
print(type(result))
通过代码执行结果发现,使用一个变量,也可以接收返回的两个数据,并且变成了一个元组。这就是组包操作。
组包与解包
组包是指将多个值赋值给一个变量时,会将多个值做为一个元组的元素,然后将元组赋值给变量。
解包是指将一个容器变量的数据赋值给多个变量时,Python 会将容器类型中的每个元素取出依次赋值给对应的变量。
解包时容器类型中的元素个数要与被赋值变量的个数相同。
示例:
# 组包
nums = 1,2,3,4,5
print(nums)
print(type(nums))
# 解包
a,b,c,d,e = nums
print(a,b,c,d,e)
通过代码可以发现,当将多个值,同时赋给一个变量时,Python 会进行自动组包操作,将所有的数字组合成一个元组,再将元组赋值给变量。 当使用一个元组为多个变量进行赋值时,Python 会将元组中的元素值,依次赋值给变量,这称为解包操作。
由此可以看出,上一小节中的函数依然返回的是一个结果值,结果值的类型是一个元组。
多个return语句
在一个函数中,允许有多个 return
语句,可以实现在不同的条件下,返回不同的值,但同一时刻,只能有一个 return
语句被执行。
def multiReturn():
return 1
return 2
return 3
return 4
return 5
print(multiReturn())
print(multiReturn())
print(multiReturn())
print(multiReturn())
print(multiReturn())
通过代码发现,虽然函数提供了 5 条 return
语句,并返回了不同的结果值,在调用时,也调用了五次,但实际,5 次的调用结果都是相同的,都为 1
。
参数传递
参数是指函数在完成特定功能时,需要外部传递的数据。
比如,现实生活中去餐厅吃饭点的菜单,打车时告诉司机的目的地,就医时告诉医生的症状等这些都是参数。
回到程序中,前面用过很多次的 len()
方法,这个方法用来对指定的数据返回其长度或元素个数,那么这个函数的功能是用来获取长度,测谁的长度,该函数并不关心,只要调用者指定的数据可测,函数就返回一个长度,而函数并不关心被测试的数据是什么。
再回到打车的例子,出租车只负责将人从一个地方拉到另一个地方,司机只关心出发地和目的地信息,而具体是谁去坐车,坐车人什么身份,为什么在出发地,去目的地做什么,这些司机都不关系,司机只负责将人拉到目的地,任务就完成了。
在程序中,函数调用时指定数据的过程,称为参数传递,这时有四个概念,如下:
主调函数
:主动调用其它函数执行的称为主调函数。被调函数
:被动调用执行的函数称为被调函数。实际参数
:在调用函数的时候,函数名称后面括号中的数据即为实际参数,简称实参,通俗讲就是实际值。形式参数
:在定义函数的时候,函数名称后面括号中的变量即为形式参数,称称形参,通俗讲就是一个记号。
在调用函数时,实参数据会依次传递给形参。
示例:
# name, age, gender 为形参
def info(name, age, gender):
print(f"我叫{name}, 年龄{age}岁,性别{gender}")
# 调用时的数据为实参
info("Tom", 22, "男")
info("Rose", 23, "女")
位置参数
位置参数也称为必备参数,必须按照正确的顺序传到函数中,即调用时的数量和位置必须 和定义时是一样的。
- 数量必须与定义时一致
在调用函数时,指定的实际参数的数量必须与形式参数的数量一致,否则将抛出
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)
关键字参数
关键字参数是指使用形式参数的名字来确定输入的参数值,通过该方式指定实际参数时,不再需要与形式参数的位置完全一致,只要将参数名写正确即可,这样可以避免用户需要牢记参数位置的麻烦,使得函数的调用和参数传递更加灵活方便。
示例:
def printMsg(n, msg):
for i in range(n):
print(f'第{i+1}次输出{msg}')
# 关键字参数
printMsg(n=5, msg="Hogworts")
printMsg(msg="Hogworts", n=5)
默认值参数
在定义函数时,形参可以定义变量一样进行赋值,这个值就是默认该参数的默认值。
调用函数时,如果指定了该参数的数据,则使用指定的数据,如果没有指定该参数的数据,则使用默认值的数据。
示例:
# 实现一个乘方函数
def my_power(m, n=2):
return m ** n
# 使用指定的参数
print(my_power(2, 3))
# 使用默认值参数
print(my_power(2))
需要注意的是,指定默认值的形式参数必须放在所有未指定默认值参数的后面,否则会产生语法错误
def show(a, b=2, c):
print(a, b, c)
可变参数
在程序运行过程中,所定义的函数,可能不能确定需要接收多少参数,有可能很多,有可能很少甚至没有。
此时,就不能通过定义固定个数的参数来接收数据,这会产生两个问题。
如果实参比形参多,则没有足够的形参支接收保存实参数据,如果实参比形参少,则形参接收不到数据无法进行函数的数据处理。
Python 中提供了可变参数的概念用来解决参数个数不确定的的情况。
可变位置参数
- Python 使用
*args
参数做为形参,接收不确定个数的位置参数。 *args
将接收到的任意多个实际参数放到一个元组中。
示例:
# 不确定个数数字求和
def my_sum(*args):
print(args)
print(*args)
print(type(args))
s = 0
for i in args:
s += i
print(s)
print("*" * 10)
my_sum(1,2,3)
my_sum(1,2,3,4)
my_sum(1,2,3,4,5)
my_sum(1,2,3,4,5,6)
可变关键字参数
- Python 使用
**kwargs
参数做为形参,接收不确定个数的关键字参数。 **kwargs
将接收到的任意多个实际参数放到一个字典中。
示例:
# 定义可变关键字参数
def print_info(**kwargs):
print(kwargs)
print(type(kwargs))
for k,v in kwargs.items():
print(k, v)
print("*" * 10)
print_info(Tom=18, Jim=20, Lily=12)
print_info(name="tom",age=22,gender="male",address="BeiJing")
混合参数
当定义函数时,参数列表中出现了多种类型的参数,定义时需要注意参数的定义顺序,如果顺序使用不正确,在调用函数时,可能会报错。
正确顺序的定义格式为:
def funcname(位置参数,可变位置参数,默认值参数,可变关键字参数):
pass
示例:
def info(name1, name2, *args, age1=18, age2=21, **kwargs):
print("Positional Arguments:")
print("name1:", name1)
print("name2:", name2)
print("args:", args)
print("Keyword Arguments:")
print("age1:", age1)
print("age2:", age2)
print("kwargs:", kwargs)
info("Alice", "Bob", "Charlie", "Dave", age2=30, occupation="Engineer", city="New York")
info("Alice", "Bob", "Charlie", "Dave", age1=25, age2=30, occupation="Engineer", city="New York")
一般函数在不确定参数的情况下,会将上面的形式简化定义,用来接收任意数量的参数。
def funcname(*args, **kwargs):
pass
在此定义形式中,使用 *args
接收所有的位置参数,使用 **kwargs
接收所有的关键字参数。
需要注意的是,传递参数时,需要先传递完所有的位置参数后,才能传递关键字参数。