# Python教程 - 6 函数
函数也可以称之为功能,我们可以将一段实现特定功能的代码封装为一个函数。
为什么需要封装为函数呢?
举个栗子:
给定2整数,我现在想获取其中的最大值。
于是乎,哐哐敲代码:
num1 = 24
num2 = 12
maxNum = None # 这里功能的代码不是很简洁,不要计较
if num1 > num2:
maxNum = num1
else:
maxNum = num2
print(f"较大的数为:{maxNum}") # maxNum得到最大的数
2
3
4
5
6
7
8
9
10
一个地方需要这个功能,我在一个地方写了这个功能的代码,100个、1000个地方需要这个功能,那就需要写1000遍。万一这个功能需要修改,修改1000个地方不得崩溃。
所以我们可以将这个功能封装为一个函数,想要使用到这个功能的时候,直接调用这个函数即可。
所以通过使用函数,提高代码的复用性,减少重复代码,提高开发效率。
我们之前已经使用了很多内置的函数,例如print()、int()、str()等,现在我们主要学习如何自定义函数。
# 6.1 函数的定义
函数定义的格式:
def 函数名(形式参数):
函数体
return 返回值
2
3
参数可以省略;
return 返回值
也可以省略,不返回结果;函数必须先定义后调用。
注意:函数内的return后面如果有语句,是不会被执行的。
函数调用的格式:
函数名(参数)
举个栗子:
下面定义一个函数,计算两个数的和,然后调用该函数。
def add(): # 定义函数
result = 3 + 5
print(f"3 + 5 = {result}")
add() # 调用函数
2
3
4
5
执行结果:
3 + 5 = 8
上面的函数只能计算 3 + 5 的值,复用性太差,我们可以让函数接收两个参数,然后根据这两个参数的和。
# 6.2 函数的参数
函数的参数可以接收外面传递的值。
通过传递参数,我们可以改写上面计算两个数的和的函数:
def add(a, b): # 定义函数,接收两个参数
result = a + b
print(f"{a} + {b} = {result}")
add(3, 5) # 调用函数
add(2, 2) # 调用函数
2
3
4
5
6
执行结果:
3 + 5 = 8 2 + 2 = 4
通过传递不同的参数,可以计算不同值的和,复用性更强。
函数名后面的参数为形式参数(形参),函数的参数个数不限,使用逗号分隔;
调用函数的时候,传递的是实际参数(实参),表示函数执行的时候的参数值。
上面函数调用时候需要根据函数定义时候的顺序来传递。
# 1 关键字参数
我们还可以通过参数名称来传递参数。
举个栗子:
def fun(a, b, c):
print(a)
print(b)
print(c)
fun(1, 2, 3) # 通过位置传参
fun(b=1, c=3, a=2) # 通过关键字传参
2
3
4
5
6
7
通过关键字传参,对参数顺序没有要求。
# 2 缺省参数
缺省参数也叫默认参数,为形参提供默认值,那么在调用函数的时候,可以不传递默认参数。
举个栗子:
def fun(a, b="abc", c=None):
print(a)
print(b)
print(c)
fun(1, 2) # 通过位置传参
fun(1, c=2) # 通过关键字传参
2
3
4
5
6
7
在调用函数的时候,有默认值的参数可以不传递参数。
需要注意:定义函数的时候,位置参数必须在默认参数前,也就是上面b定义了默认参数,c必须也指定默认参数。
执行结果:
1 2 None 1 abc 2
# 3 序列实参
序列实参就是将列表作为参数传递给函数,列表中的元素依次对应函数的参数。
举个栗子:
def fun(a, b, c):
print(a)
print(b)
print(c)
list = [1, 2, 3]
fun(*list) # 需要在列表前面添加一个星号
2
3
4
5
6
7
调用的时候,需要在列表前面添加一个星号。
执行结果:
1
2
3
需要注意,列表元素的个数不能大于形参的个数。
# 4 字典实参
字典实参就是将字典作为参数传递给函数,字典中的元素与函数的参数对应。
举个栗子:
def fun(a, b, c):
print(a)
print(b)
print(c)
dict01 = {"a": 1, "c": 3, "b": 2}
fun(**dict01)
2
3
4
5
6
7
需要在字典前面添加两个星号。
执行结果:
1 2 3
注意,字典中不能包含函数没有对应的key。
# 5 单星号元组形参
单星号元组形参就是将函数所有的参数合并成一个元组,这个可以让函数的参数个数没有限制。
举个栗子:
def fun(*args): # args 是一个元组,接收任意个数的参数
for arg in args:
print(arg)
fun()
fun(1)
fun(1, "a")
fun(1, "a", 2)
2
3
4
5
6
7
8
参数名称建议使用args,调用函数的时候,参数的格式是没有限制的。
在元组形参之后的参数,在调用的时候,必须使用关键字参数。
def fun(a, *args, b):
print(a)
print(args)
print(b)
fun(1, "a", 2, b=3) # 给参数b传递值,必须使用关键字进行传参。
2
3
4
5
6
执行结果:
1 ('a', 2) 3
如果想要在参数传递的时候,必须使用关键字参数,可以使用如下方式定义函数:
def fun(*, a, b):
print(a)
print(b)
fun(a=1, b=3) # 传递参数的时候,*的位置忽略
2
3
4
5
这样在调用函数的时候,参数必须使用关键字进行传参,*的位置不用传递参数。
# 6 双星号字典形参
双星号字典形参,可以传递数量不限的关键字实参。
举个栗子:
def fun(**kwargs): # kwargs就是一个字典
print(kwargs)
fun()
fun(a=1)
fun(a=1, b=2)
2
3
4
5
6
形参就是一个字典,实参可以传递任意数量的关键字实参。
执行结果:
{} {'a': 1} {'a': 1, 'b': 2}
# 6.3 函数的返回值
上面定义的函数都是没有返回值的,但是在实际使用中,我们是希望函数返回一个结果的。
例如我们计算两个数的和,我们希望将计算的结果返回给我,因为后面我可能还会用到这个结果。
举个栗子:
def add(a, b): # 定义函数
result = a + b
return result # 将结果返回
sum1 = add(3, 5) # 定义一个变量接收返回的结果
print(sum1)
2
3
4
5
6
上面定义了一个函数,将两个数相加,然后将结果返回,并打印出来。
注意:函数内的return后面如果有语句,是不会被执行的。
执行结果:
12
函数的返回值需要定义变量来接收,不过,如果你只是调用,不接收结果,从语法上来说也是没有问题的,只是这里没有什么效果:
def add(a, b): # 定义函数
return a + b # 将结果返回
add(3, 5)
2
3
4
# 1 多个返回值
函数还可以同时返回多个返回值,这在其他语言中是做不到的。
举个栗子:
# 定义了一个函数,用来返回最小值和最大值
def get_min_and_max(num1, num2):
if num1 > num2:
return num2, num1 # 同时返回两个返回值,第一个是最小值,第二个是最大值
else:
return num1, num2
min_num, max_num = get_min_and_max(20, 10) # 得到两个返回值
print(min_num)
print(max_num)
2
3
4
5
6
7
8
9
10
11
执行结果:
10
20
返回多个返回值,其实是使用元组的方式来返回的,然后赋值给多个变量。
# 2 None返回值
如果一个函数没有返回值,那么函数有返回值吗?
是有的,类型为None。
def add(a, b):
a + b
value = add(3, 5)
print(value)
print(type(value))
2
3
4
5
6
执行结果:
None
<class 'NoneType'>
None也可以主动返回:
def add(a, b):
return None
value = add(3, 5)
print(value)
print(type(value))
2
3
4
5
6
执行结果:
None
<class 'NoneType'>
# 6.4 函数说明文档
有些代码1个月后你重新看,你可能就会有感慨,这狗屎代码是TM谁写的。函数有时候参数很多,逻辑也比较复杂,时间长了就忘记这个函数封装的是什么功能,每个参数的作用是什么。
所以我们就需要给函数添加说明文档,帮助我们理解函数的作用和各个参数的含义。
函数的说明文档,格式如下:
def 函数名(x, y):
"""
函数说明
:param x: 形参x的说明
:param y: 形参y的说明
:return: 返回值的说明
"""
函数体
return 返回值
2
3
4
5
6
7
8
9
- 通过多行注释对函数进行说明,并且内容写在函数体之前。
- :param 用于对参数进行解释
- :return 用于对返回值进行解释
你可能会有疑问,这只是个说明文字,写在哪,什么格式有什么区别呢?
莫急,举个栗子:
在PyCharm中编写代码如下:
当我们写完函数后面的 冒号:
,然后回车,然后在第二行输入三个 **"
** 回车,会发现PyCharm会自动生成下面的函数说明文档的结构。只需要补全自己的说明文字就可以了。
当我们写完函数及说明文档后,在调用函数的时候,我们将鼠标悬停在函数名上,会显示要调用的函数的说明文档:
这样我们就知道函数的作用以及传递参数的含义。
# 6.5 函数的嵌套调用
函数的嵌套调用就是一个函数可以调用另外一个函数,另外一个函数还可以继续调用其他的函数,依此类推。
举个栗子:
下面定义了2个函数,fun_a() 和 fun_b(),并在fun_a() 中调用了 fun_b()。然后我们调用 fun_a() 执行。
def fun_b():
print("----b")
def fun_a():
print("----a1")
fun_b()
print("----a2")
fun_a()
2
3
4
5
6
7
8
9
执行结果:
----a1 ----b ----a2
我们会发现在 fun_a() 中调用 fun_b() 后,fun_b() 执行完成,重新回到了 fun_a() 继续执行。
# 6.6 变量的作用域
变量的作用域就是在哪里可以使用这个变量。不可能我们随便定义了一个变量,哪里都可以使用。
变量主要分类两类:局部变量和全局变量。
# 1 局部变量
局部变量就是在函数内部定义的变量,这样变量只能在变量内部使用。
举个例子:
def fun_a():
num = 10
print(num)
fun_a()
print(num)
2
3
4
5
6
执行结果:
10 Traceback (most recent call last): File "hello-python/hello_world.py", line 6, in <module> print(num) ^^^ NameError: name 'num' is not defined. Did you mean: 'sum'?
因为变量num在 fun_a() 函数内部定义,所以在函数外面访问会报错。
局部变量当函数调用完成,就被销毁释放了。
# 2 全局变量
全局变量就是在函数内、外都内调用的变量。
举个栗子:
num = 10
def fun_a():
print(f"函数内:{num}")
fun_a()
print(f"函数外:{num}")
2
3
4
5
6
7
执行结果:
函数内:10 函数外:10
# 3 globa 关键字
我们将上面的代码修改一下:
num = 10
def fun_a():
num = 20
print(f"函数内:{num}")
fun_a()
print(f"函数外:{num}")
2
3
4
5
6
7
8
我们先调用了 fun_a(),然后在 fun_a() 中修改了 num 的值,然后在函数外又打印了 num,猜猜输出结果。
执行结果:
函数内:20 函数外:10
真是纱布擦屁股,给我漏了一手。命名已经在函数内部修改了变量 num,为什么后来打印的还是之前的值?
这是因为在函数内部的 num 和函数外部的 num 不是同一个num。
如果要在函数内部修改全局变量,需要在函数内部添加 global 关键字。
num = 10
def fun_a():
global num
num = 20
print(f"函数内:{num}")
fun_a()
print(f"函数外:{num}")
2
3
4
5
6
7
8
9
通过 global 关键字,告诉程序,我这个是全局变量。
执行结果:
函数内:20 函数外:20
# 6.7 函数的递归
什么是函数的递归?
函数的递归,就是在一个函数内部,又调用了这个函数自己,这个就是函数的递归。
举个例子:
def fun_a():
print("----")
fun_a()
fun_a()
2
3
4
5
上面的函数,执行 fun_a(),然后在 fun_a() 内部又调用了自己,那么会重新又调用了 fun_a() 函数,然后又调用了自己,这样就会一直无限调用,变成了死循环。
所以函数的递归有时候是很危险的,很容易无限调用,造成栈溢出,程序崩溃。所以函数的递归调用一定要注意结束或跳出递归的条件。
例如我们写一个用递归求阶乘的函数:
def factorial(num): # 求阶乘
if num <= 1:
return 1
return num * factorial(num - 1)
num = int(input("请输入数字:"))
result = factorial(num)
print(f"{num}! = {result}")
2
3
4
5
6
7
8
9
10
以 5 举例,求 5 的阶乘,调用了 factorial函数
,则计算 5 乘以 4 的阶乘,然后求 4 的阶乘,重新调用了 factorial函数
,然后计算 4 乘以 3 的阶乘,一次类推,一直得到1的阶乘,然后向上返回。
当输入5的时候,执行结果:
请输入数字:5 5! = 120
递归函数一定得有结束的条件,否则就会无限递归。
# 6.8 函数的内存结构
先看下面的代码:
def fun(a):
a = 100
num = 1
fun(num) # 调用函数能否修改num的值
print(num)
2
3
4
5
6
执行结果:
1
可以发现虽然调用了fun函数,但是并没有修改num的值,这是为什么呢?
画一下内存图就一目了然了:
首先代码从上面执行,遇到函数 fun(a)
,此时是不执行 fun(a)
的,而是将 fun(a)
加载到方法区。
方法区主要是存储代码的。
然后执行 num = 1
,创建了一个变量num,指向了1,结果:
然后执行 fun(num)
,调用方法 fun(num)
先从方法区找到代码,然后需要创建一个方法的栈帧,在栈帧中存储方法中的变量。
调用方法 fun(num)
,将 num 传递给了 a,所以 a 指向了1。
然后执行 fun(num)
方法中的 语句 a = 100
,则 a 指向了100
此时执行 print(num)
打印 num,num的值还是1。
# 6.9 类型注解
我们在PyCharm中使用随机数模块的时候,在调用随机整数的时候,会提示传入数据的类型:
但是我自己写的方法,怎么就没有呢?
怎么能提示传入数据的类型呢?
在Python3.5版本后引入了类型注解,可以帮助开发工具进行类型推断,可以做代码提示。
目前支持对变量、方法参数和返回值类型的注解。
语法:
变量:类型
# 1 变量类型注解
基础类型注解:
num1: int = 10
num2: float = 3.14
num3: bool = True
num4: str = "Hello"
2
3
4
对象类型注解:
class Student:
pass
stu: Student = Student();
2
3
4
容器类型注解:
my_list: list = [1, 2, 3]
my_tuple: tuple = (1, True, "hello")
my_set: set = {1, 2, 3}
my_dict: dict = {"age":18}
2
3
4
容器类型详细注解:
my_list: list[int] = [1, 2, 3]
my_tuple: tuple[int, bool, str] = (1, True, "hello")
my_set: set[int] = {1, 2, 3}
my_dict: dict[str, int] = {"age":18}
2
3
4
注意:元组类型详细注解,需要将每一个元素的类型都标记出来。
字典类型注解需要设置2个类型,分别是key和value。
除了使用 变量: 类型
这种注解语法,还可以在注释中进行注解,例如:
num1 = 10 # type: int
num2 = 3.14 # type: float
num3 = "Hello" # type: str
2
3
一般我们给变量添加注解的时候,如果能直接看出类型来,就不用添加注解了,如果不能看出类型,在添加变量注解。
num = 10 # 一下就看出来num是个整形
obj: Student = func() # 看不出来obj是个什么类型,那么建议给obj添加类型直接。
2
注意:注解只是用来提示的,不是验证的,写错了也不会报错的。
# 2 函数类型注解
函数类型注解语法如下:
def 函数名(形参1: 类型, ...., 形参N: 类型) -> 返回值类型:
pass
2
举个栗子:
def fun(a: int, b: int) -> int:
return a + b
fun()
2
3
4
添加了函数类型注解,在使用的时候,就会有类型提示了:
函数类型注解可以按需添加,哪个需要添加哪个。
# 3 Union类型
什么是Union类型。
我们之前给集合添加注解类型,那么这个集合只能有一个类型,如果集合中的元素类型有多种,就可以使用Union类型注解,用来定义多重元素类型。
举个栗子:
首先需要先引入Union模块。
from typing import Union
my_list: list[Union[int, str]] = [1, "name"] # 定义了list中的元素是多种数据类型
my_dict: dict[str, Union[int, str]] = {"name": "zhangsan", "age": 18} # 定义了value可以是多种类型
2
3
4
同样,Union联合类型注解,也可以用在函数参数和返回值中。
def fun(data: Union[int, str]) -> Union[int, str]: # 定义返回值的类型注解
pass
def fun(data: Union[int, str]) -> list[Union[int, str]]: # 定义list返回值的类型注解
pass
2
3
4
5