函数返回值与参数处理
大家好!这个章节我们来聊聊函数的返回值和参数如何处理。你可以把函数想成是一个工厂,它接收一些输入数据,也就是参数,然后按照规定的流程也就是函数体,生产出结果也就是返回值。接下来,我们来了解一下如何通过返回值来传递结果,如何处理参数,特别是那些不确定个数的参数。让我们一起来看看吧!
简介
在前面讲解了函数的基本定义和使用,通过一段时间的使用,对函数的使用有了一定的了解。
但是实际函数使用时,基本的定义和调用还远远没有达到函数的使用要求。
函数的主要作用是实现模块化代码,减少代码在程序中的冗余,提高代码的复用率。
函数通过接收一定的数据,根据业务逻辑进行相应的处理,然后将处理结果返回给调用者。

在之前,我们已经学过了函数的基本定义和使用。我们知道,函数的作用就是把一段代码封装起来,方便我们在不同的地方重复使用它。不过,函数不仅仅是定义和调用这么简单。在实际编程中,我们要学会如何通过函数来模块化代码,避免重复劳动,提高代码复用率。函数不仅仅接收数据并做处理,最终还需要把结果带回给调用者。
函数思想
实现函数要基于高内聚,低耦合的思想。
在设计函数时,我们需要遵循两个核心思想:高内聚和低耦合。这就像做菜时,你要确保每道菜的味道都很鲜明,同时每道菜之间不要互相干扰,才能做出既好吃又协调的菜单。
函数高内聚
高内聚低耦合指的是函数内部的代码要有高内聚性,而函数之间要保持低耦合性。
高内聚性指的是函数内部的代码逻辑和功能彼此相关,实现的是一个具体的功能或目标,各个部分之间密切配合。
一个高内聚的函数通常只完成一个明确的任务,并且其内部的代码逻辑清晰、简洁。
高内聚就是要让函数内部的代码尽可能地集中、紧凑,它应该完成一个明确的任务。就像你做一个菜,应该专注做这个菜,不能一边做菜一边做其他事。一个高内聚的函数应该是简单的,易于理解和修改的。
函数低耦合
低耦合性指的是函数之间的依赖关系较弱,相对独立,一个函数的改动不会对其他函数造成过多的影响。
低耦合的函数在设计上应该尽量减少使用全局变量或直接修改其他函数的参数,而是通过参数传递和返回值来进行数据交互。
低耦合则是指,函数之间的依赖关系要尽量松散。如果一个函数改动了,其他函数不应该受到影响。比如,你做了一个菜,尽量不要把它做得太复杂,依赖其他菜品的味道,这样如果改动其中某一道菜,其他菜就不需要一起修改。
高内聚低耦合优点
通过遵循函数的高内聚低耦合原则,可以带来以下优点:
-
可维护性:函数内部的代码高度相关和一致,使得函数更易于理解、调试和修改,减少了不相关的代码耦合。
-
可测试性:函数的高内聚性使得它们更易于独立地进行单元测试,因为它们具有清晰的功能和输入输出。
-
可复用性:函数之间的低耦合性使得它们可以在不同的上下文中被重复使用,提高了代码的复用性。
-
灵活性:函数之间相互独立且功能明确,使得系统更容易进行扩展和修改,不会对其他函数产生很大的影响。
遵循高内聚低耦合的原则,我们可以让代码更加清晰、易维护,而且能提高可复用性。每个函数都做自己擅长的事,并且可以在不同的项目中复用。这就像一个餐厅菜单,每道菜都有自己的风味,不会互相影响,客人也能根据口味选择自己想要的。
在设计函数时,应该关注其功能的内聚性,确保函数只完成一个特定的任务,并且将其功能按照模块化的方式进行拆分。同时,尽量减少函数之间的直接依赖和共享状态,通过参数传递和返回值来进行数据交互,以减少函数之间的耦合度。
通过保持函数的高内聚低耦合,能够提高代码的可维护性、可测试性和可复用性,同时使得系统更具灵活性和可扩展性。
设计函数时,我们要确保函数只完成一个具体的任务,避免过度依赖其他函数。这样既能提高函数的可维护性和可测试性,也能增加代码的复用性和灵活性。
函数返回值
在设计一个函数时,可以通过函数的返回值,将函数功能的处理结果,返回给调用者。
而一个真正完整的函数,需要返回值将结果返回给调用者,而不是直接通过 print 函数将其输出。
比如,存在一个可以查找列表中最大数值的函数,而刚好调用者有需求在一个列表中要找到最大值,并且进行后续操作。
如果此时查找最大值函数没有返回结果而是直接输出的话,对于调用者来说,该函数没有任何意义。
Python 中使用 return 返回函数的结果,同时,return 也具有结束函数的作用。
在设计函数时,记得通过 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 值。
当函数执行到 return 语句时,它会立即结束。就像你做饭时,如果做完一部分就把菜端上桌,厨房的其他工作也不再继续。函数执行后,默认返回的是 None,除非你指定了返回值。在这段代码中,当循环的 i 等于 2 时,执行 return,函数立刻结束,后面的内容不会再执行。所以,我们会看到 循环后输出内容 这行没有输出。
函数只能返回一个值
一个函数只能返回一个结果值,当需要返回多个值时,需要进行组包处理。
此函数用来从键盘获取一个字符串,并返回给了调用者。调用者得到结果后进行了大写处理。
def get_string():
s = input("请输入一个字符串:")
return s
result = get_string()
print(result.upper())
Python 函数一次只能返回一个值。比如这个函数 get string 从键盘获取一个字符串,并返回给了调用者。调用者得到结果后进行了大写处理。
如果有两个或更多的值想返回,都写在 return 后行不行呢?
通过代码发现,函数执行结果和前面介绍的理论并不符合,但实际上,该函数返回的依然是一个值,只是 Python 在执行时,自动做了组包和解包操作,将多个值默认包装成一个元组返回。
def get_two_num():
a = int(input("请输入第一个数字:"))
b = int(input("请输入第二个数字:"))
return a, b
m, n = get_two_num()
print(m, n)
如果你需要返回多个值,可以通过组包的方式来返回一个容器,比如元组。例如,如果你想返回两个数字,你可以用 return 同时返回它们。这段代码会输出一个元组,包含两个数字。实际上,我们是返回了一个值——这个值是一个元组。
def get_two_num():
a = int(input("请输入第一个数字:"))
b = int(input("请输入第二个数字:"))
return a, b
result = get_two_num()
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 会将元组中的元素值,依次赋值给变量,这称为解包操作。
由此可以看出,上一小节中的函数依然返回的是一个结果值,结果值的类型是一个元组。
通过代码可以发现,当将多个值,同时赋给一个变量时,Python 会进行自动组包操作,将所有的数字组合成一个元组,再将元组赋值给变量。当使用一个元组为多个变量进行赋值时,Python 会将元组中的元素值,依次赋值给变量,这称为解包操作。在这个例子中,nums 是一个元组,我们通过解包将其元素依次赋给了 a、b、c、d 和 e。
多个 return 语句
在一个函数中,允许有多个 return 语句,可以实现在不同的条件下,返回不同的值,但同一时刻,只能有一个 return 语句被执行。
通过代码发现,虽然函数提供了 5 条 return 语句,并返回了不同的结果值,在调用时,也调用了五次,但实际,5 次的调用结果都是相同的,都为 1 。
def multi_return():
return 1
return 2
return 3
return 4
return 5
print(multi_return())
print(multi_return())
print(multi_return())
print(multi_return())
print(multi_return())
在一个函数中,你可以有多个 return 语句,根据不同的条件返回不同的结果。就像在餐厅里,可能有多个菜单,客人选择了不同的菜单,得到不同的菜品。需要注意的是,虽然有多个 return 语句,实际执行时,函数只会返回一个结果,其他的都不会执行。这段代码的输出总是 1,因为一旦执行了第一个 return,后面的语句就不再执行了。
参数传递
参数是指函数在完成特定功能时,需要外部传递的数据。
比如,现实生活中去餐厅吃饭点的菜单,打车时告诉司机的目的地,就医时告诉医生的症状等这些都是参数。
回到程序中,前面用过很多次的 len() 方法,这个方法用来对指定的数据返回其长度或元素个数,那么这个函数的功能是用来获取长度,测谁的长度,该函数并不关心,只要调用者指定的数据可测,函数就返回一个长度,而函数并不关心被测试的数据是什么。
再回到打车的例子,出租车只负责将人从一个地方拉到另一个地方,司机只关心出发地和目的地信息,而具体是谁去坐车,坐车人什么身份,为什么在出发地,去目的地做什么,这些司机都不关系,司机只负责将人拉到目的地,任务就完成了。
在实际编程中,函数通常需要外部数据来完成特定的任务,这些数据就是参数。想象你去餐厅点餐,告诉服务员你想要点的菜,就是把参数传递给厨师。函数也是如此,它通过参数获取外部数据并进行处理。
在程序中,函数调用时指定数据的过程,称为参数传递,这时有四个概念,如下:
主调函数:主动调用其它函数执行的称为主调函数。被调函数:被动调用执行的函数称为被调函数。实际参数:在调用函数的时候,函数名称后面括号中的数据即为实际参数,简称实参,通俗讲就是实际值。形式参数:在定义函数的时候,函数名称后面括号中的变量即为形式参数,称称形参,通俗讲就是一个记号。
在函数调用时,我们通常有这四个概念。主动调用其它函数执行的称为主调函数。被动调用执行的函数称为被调函数。实际参数是调用时传递的真实数据,而形式参数则是在函数定义时给定的占位符。
在调用函数时,实参数据会依次传递给形参。
# 示例
# name, age, gender 为形参
def info(name, age, gender):
print(f"我叫{name}, 年龄{age}岁,性别{gender}")
# 调用时的数据为实参
info("Tom", 22, "男")
info("Rose", 23, "女")
在调用函数时,实参数据会依次传递给形参。比如这个例子中,我们在调用 info 函数是,如果传入的是 "Tom", 22, "男"。那么就代表 name 的值为 Tom,age 的值为 22,gender 的值为 男。实参的值是按照形参的顺序去依次赋值的。
位置参数
位置参数也称为必备参数,必须按照正确的顺序传到函数中,即调用时的数量和位置必须 和定义时是一样的。
- 数量必须与定义时一致。
在调用函数时,指定的实际参数的数量必须与形式参数的数量一致,否则将抛出TypeError异常,提示缺少必要的位置参数missing x required positional argument。 - 位置必须与定义时一致。在调用函数时,指定的实际参数的位置必须与形式参数的位置一致,否则将抛出
TypeError异常或者结果与预期不符的情况,例如入参的位置 1 需要一个int类型参数,而入参 2 位置需要一个str类型的参数,如果传递的位置不正确,那么str类型数据传递进去之后会报类型错误的异常
位置参数是最基本的参数类型。它要求调用函数时,实参必须按顺序传递给形参。如果传递位置不对,或者数量不一致,就会出错。
位置参数是会按照顺序将实际函数对应传递给形式参数。
# 示例
def print_msg(n, msg):
for i in range(n):
print(f'第{i+1}次输出{msg}')
# 正确使用位置参数
print_msg(5, "Hogworts")
# 错误使用位置参数
print_msg("Hogworts", 5)
比如这两个例子。我们定义了一个 print_msg 函数。它需要接受 2 个参数分别是 n 和 msg。n 代表打印的次数,msg 代表要打印的信息内容。当传入 5 和 Hogwarts 这个字符串的时候,因为位置参数是会按照顺序将实际函数对应传递给形式参数,所以 n 的值是 5,msg 的值是 Hogwarts,所以程序可以正确执行。如果传递的位置不对,比如先传入一个字符串,再传入一个数字,因为顺序颠倒了,msg 本来应该是一个字符串,而 n 是数字。这样就会导致程序内部处理的数据类型错误而导致报错了。
关键字参数
关键字参数是指使用形式参数的名字来确定输入的参数值,通过该方式指定实际参数时,不再需要与形式参数的位置完全一致,只要将参数名写正确即可,这样可以避免用户需要牢记参数位置的麻烦,使得函数的调用和参数传递更加灵活方便。
# 示例
def print_msg(n, msg):
for i in range(n):
print(f'第{i+1}次输出{msg}')
# 关键字参数
print_msg(n=5, msg="Hogworts")
print_msg(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 提供了可变参数来解决这个问题,允许我们在函数调用时传入任意数量的参数。
可变位置参数
- 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 中可以使用 星args。它可以把传入的所有位置参数收集到一个元组中。想象你去餐馆吃饭,告诉服务员你要吃多少道菜,而服务员则根据你的需求将每道菜的名字放到一个菜单上,最后根据菜单给你上菜。来看一下代码例子。在这个例子中,my_sum 函数接收了不同数量的参数,每次调用时,我们可以看到函数都将这些参数转成了一个元组 args,然后对它们进行求和操作。注意,星args 就是让我们能够接受任意数量的位置参数,并且它们会被存储在一个元组里。
可变关键字参数
- 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")
接下来,Python 还有一种更强大的可变参数——可变关键字参数。如果你需要在函数中接收不确定数量的关键字参数,可以使用 星星kwargs。星星kwargs 会把传入的关键字参数收集成一个字典。举个例子,如果你想把个人信息(如名字、年龄等)传递给一个函数,而这些信息可能有很多,使用关键字参数就能很方便地传递。在这个例子中,print info 函数接收了任意数量的关键字参数,并将它们存储在一个字典中。每个键值对代表一个参数和对应的值。通过 kwargs点items,我们可以轻松地遍历这些键值对。
混合参数
当定义函数时,参数列表中出现了多种类型的参数,定义时需要注意参数的定义顺序,如果顺序使用不正确,在调用函数时,可能会报错。
正确顺序的定义格式为:
def funcname(位置参数,可变位置参数,默认值参数,可变关键字参数):
pass
那么,如果你的函数既需要接收位置参数,又需要接收可变位置参数、默认值参数、可变关键字参数呢?这里就涉及到混合参数的问题。其实,Python 允许你在同一个函数中同时使用这几种不同类型的参数,但是它有一个规则:参数的定义顺序是非常重要的。如果顺序不对,Python 会报错。正确的顺序是:位置参数、可变位置参数 星args、默认值参数、可变关键字参数 星星kwargs。简单来说,就是先处理明确的、固定的参数,再处理可变数量的参数。
示例:
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")
再来看个例子。这里我们有一个 info 函数,接受不同类型的参数。name1 和 name2 是位置参数,星args 用来接收其他位置参数,而 age1 和 age2 是带有默认值的参数,星星kwargs 用来接收所有的关键字参数。注意,调用时,位置参数总是最先传递,然后是默认值参数,最后是关键字参数。
一般函数在不确定参数的情况下,会将上面的形式简化定义,用来接收任意数量的参数。
在此定义形式中,使用 *args 接收所有的位置参数,使用 **kwargs 接收所有的关键字参数。
需要注意的是,传递参数时,需要先传递完所有的位置参数后,才能传递关键字参数。
def funcname(*args, **kwargs):
pass
如果我们在定义函数的时候,确实不确定参数的情况,那么可以简化上面的形式,来接收任意数量的参数。比如直接使用 星args 接收所有的位置参数,使用 星星kwargs 接收所有的关键字参数就可以了。
总结
- 函数思想
- 函数返回值
- 参数传递
- 可变参数
- 混合参数
最后来总结一下,我们讨论了函数中的可变参数和混合参数。通过 args 和 *kwargs,我们可以让函数接收不定数量的参数,保证了函数的灵活性。同时,混合参数的使用让我们可以根据需求灵活定义函数,满足不同场景的需求。希望大家能在实际编程中多加练习,灵活运用这些知识!