# Python教程 - 6 函数

函数也可以称之为功能,我们可以将一段实现特定功能的代码封装为一个函数。

为什么需要封装为函数呢?

举个栗子:

给定2整数,我现在想获取其中的最大值。

于是乎,哐哐敲代码:

num1 = 24
num2 = 12

maxNum = None											# 这里功能的代码不是很简洁,不要计较
if num1 > num2:										
    maxNum = num1
else:
    maxNum = num2

print(f"较大的数为:{maxNum}")			# maxNum得到最大的数
1
2
3
4
5
6
7
8
9
10

一个地方需要这个功能,我在一个地方写了这个功能的代码,100个、1000个地方需要这个功能,那就需要写1000遍。万一这个功能需要修改,修改1000个地方不得崩溃。

所以我们可以将这个功能封装为一个函数,想要使用到这个功能的时候,直接调用这个函数即可。

所以通过使用函数,提高代码的复用性,减少重复代码,提高开发效率。

我们之前已经使用了很多内置的函数,例如print()、int()、str()等,现在我们主要学习如何自定义函数。

# 6.1 函数的定义

函数定义的格式:

def 函数名(形式参数):
    函数体
    return 返回值
1
2
3
  • 参数可以省略;

  • return 返回值 也可以省略,不返回结果;

  • 函数必须先定义后调用。

  • 注意:函数内的return后面如果有语句,是不会被执行的。

函数调用的格式:

函数名(参数)
1

举个栗子:

下面定义一个函数,计算两个数的和,然后调用该函数。

def add():										# 定义函数
    result = 3 + 5
    print(f"3 + 5 = {result}")

add()													# 调用函数
1
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)													# 调用函数
1
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)          # 通过关键字传参
1
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)            # 通过关键字传参
1
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)              # 需要在列表前面添加一个星号
1
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)
1
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)
1
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传递值,必须使用关键字进行传参。
1
2
3
4
5
6

执行结果:

1 ('a', 2) 3

如果想要在参数传递的时候,必须使用关键字参数,可以使用如下方式定义函数:

def fun(*, a, b):
    print(a)
    print(b)

fun(a=1, b=3)			# 传递参数的时候,*的位置忽略
1
2
3
4
5

这样在调用函数的时候,参数必须使用关键字进行传参,*的位置不用传递参数。

# 6 双星号字典形参

双星号字典形参,可以传递数量不限的关键字实参。

举个栗子:

def fun(**kwargs):   # kwargs就是一个字典
    print(kwargs)

fun()
fun(a=1)
fun(a=1, b=2)
1
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)
1
2
3
4
5
6

上面定义了一个函数,将两个数相加,然后将结果返回,并打印出来。

注意:函数内的return后面如果有语句,是不会被执行的。

执行结果:

12

函数的返回值需要定义变量来接收,不过,如果你只是调用,不接收结果,从语法上来说也是没有问题的,只是这里没有什么效果:

def add(a, b):						# 定义函数
    return a + b					# 将结果返回			

add(3, 5)
1
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)
1
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))
1
2
3
4
5
6

执行结果:

None

<class 'NoneType'>

None也可以主动返回:

def add(a, b):
    return None

value = add(3, 5)
print(value)
print(type(value))
1
2
3
4
5
6

执行结果:

None

<class 'NoneType'>

# 6.4 函数说明文档

有些代码1个月后你重新看,你可能就会有感慨,这狗屎代码是TM谁写的。函数有时候参数很多,逻辑也比较复杂,时间长了就忘记这个函数封装的是什么功能,每个参数的作用是什么。

所以我们就需要给函数添加说明文档,帮助我们理解函数的作用和各个参数的含义。

函数的说明文档,格式如下:

def 函数名(x, y):
    """
    函数说明
    :param x: 形参x的说明
    :param y: 形参y的说明
    :return: 返回值的说明
    """
    函数体
    return 返回值
1
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()
1
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)
1
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}")
1
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}")
1
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}")
1
2
3
4
5
6
7
8
9

通过 global 关键字,告诉程序,我这个是全局变量。

执行结果:

函数内:20 函数外:20

# 6.7 函数的递归

什么是函数的递归?

函数的递归,就是在一个函数内部,又调用了这个函数自己,这个就是函数的递归。

举个例子:

def fun_a():
    print("----")
    fun_a()

fun_a()
1
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}")
1
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)
1
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

# 1 变量类型注解

基础类型注解:

num1: int = 10
num2: float = 3.14
num3: bool = True
num4: str = "Hello"
1
2
3
4

对象类型注解:

class Student:
    pass

stu: Student = Student();
1
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}
1
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}
1
2
3
4

注意:元组类型详细注解,需要将每一个元素的类型都标记出来。

字典类型注解需要设置2个类型,分别是key和value。

除了使用 变量: 类型 这种注解语法,还可以在注释中进行注解,例如:

num1 = 10           # type: int
num2 = 3.14         # type: float
num3 = "Hello"      # type: str
1
2
3

一般我们给变量添加注解的时候,如果能直接看出类型来,就不用添加注解了,如果不能看出类型,在添加变量注解。

num = 10								# 一下就看出来num是个整形
obj: Student = func()		# 看不出来obj是个什么类型,那么建议给obj添加类型直接。
1
2

注意:注解只是用来提示的,不是验证的,写错了也不会报错的。

# 2 函数类型注解

函数类型注解语法如下:

def 函数名(形参1: 类型, ...., 形参N: 类型) -> 返回值类型:
    pass
1
2

举个栗子:

def fun(a: int, b: int) -> int:
    return a + b

fun()
1
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可以是多种类型
1
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
1
2
3
4
5