第6章 函数
在前面几个章节中我们经常使用到 print(),那么它是什么呢?print() 是一个函数,可以向控制台打印输出内容。
6.1 函数的概念
函数是带名字的代码块,用于完成具体的任务,可重复使用。当需要在程序中多次执行同一项任务时,无须反复编写完成该任务的代码,只需要调用执行该任务的函数,让 Python 运行其中的代码即可。
Python 中的函数必须先定义后使用,Python 提供了许多内建函数,比如 print()。也可以自己创建函数,这被叫做用户自定义函数。
6.2 函数的定义
6.2.1 语法
def 函数名(参数列表):
函数体
[return]
6.2.2 定义一个函数的规则
- 函数代码块以
def关键词开头,后接函数标识符名称和圆括号()。 - 任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。
- 函数的第一行语句可以选择性地使用文档字符串(用三个引号括起来)。
- 函数参数后面以冒号结束。
- 函数体开始缩进。
return [表达式]结束函数,选择性地返回一个值给调用方。不带表达式的 return 相当于返回 None。
6.2.3 函数名
函数名是程序员给这个函数起的名称,需要遵循标识符的命名规则。函数名一般是一个动词,第一个单词小写,其他每个单词的首字母大写。
6.2.4 参数
函数在完成某个功能时,可能需要一些数据,在定义函数时指定函数参数来接收这些数据。如果有多个参数,参数之间使用逗号分隔。函数也可以没有参数,但是小括弧不能省略。
6.2.5 函数体
调用函数时执行的代码,可包含函数说明文档与返回值。
6.2.6 返回值
有些函数在执行的时候,需要有返回值给调用者,通过 return 关键字进行返回,返回到函数调用的位置。
6.3 函数的抽取以及调用
函数定义好之后,通过 函数名() 对函数进行调用。
# 定义一个函数,该函数完成打印输出2*3 "*"的功能
def printStar():
'''这是对函数功能的说明'''
row = 2
while row > 0:
print("*" * 3)
row -= 1
# 调用函数
printStar()
print("-" * 20)
printStar()
注意: 函数必须先定义再调用。函数在定义的时候只是告诉解释器定义了一个这样的函数,需要调用函数后,才会执行。
6.4 使用函数的好处
- 使程序变得更简短而清晰
- 可以提高程序开发的效率
- 提高了代码的重用性
- 便于程序分工协作开发
- 便于代码的集中管理
- 有利于程序维护
6.5 函数的参数
6.5.1 参数的抽取
通过函数的参数接收行和列数据(形参),在调用函数的时候把行和列数据作为参数传递过来(实参)。
def printStar(row, col):
while row > 0:
print("*" * col)
row -= 1
printStar(2, 3)
print("-" * 20)
printStar(1, 4)
6.5.2 形参和实参
- 在定义函数时,指定的参数称为形式参数(形参)—— 函数的提供者
- 在调用函数时,给函数传递的参数称为实际参数(实参)—— 函数的调用者
- 在定义函数时,形参没有分配存储空间,也没有值,相当于一个占位符
- 当函数执行结束,函数所占的栈空间会被释放,函数的形参/局部变量也会被释放
6.5.3 函数的参数传递
Python 中一切都是对象,严格意义我们不能说值传递还是引用传递,我们应该说传不可变对象和传可变对象。
- 不可变类型:类似值传递,如整数、字符串、元组。传递的只是值的副本,不影响原对象。
- 可变类型:类似引用传递,如列表、字典。修改后外部的变量也会受影响。
# 传不可变对象
def changeInt(a):
a = 10
b = 2
changeInt(b)
print(b) # 2
# 传可变对象
def changeList(myList):
myList[1] = 50
mlist = [1, 2, 3]
changeList(mlist)
print(mlist) # [1, 50, 3]
var1 *= 2与var1 = var1 * 2的区别:var1 *= 2使用原地址(对可变类型),var1 = var1 * 2开辟了新的空间。
6.5.4 函数可使用的参数形式
1)必须参数
基于位置把每个相应位置的实参和形参相关联,调用时的数量必须和声明时的一样。
def func(a, b, c):
print(a, b, c)
func(1, 2, 3) # 1 2 3
2)关键字参数
函数调用使用关键字参数来确定每个变量传入的参数值,允许参数的顺序与声明时不一致。
def printInfo(name, age):
print("姓名:", name)
print("年龄:", age)
printInfo(name="zhangsan", age=18)
printInfo(age=18, name="zhangsan")
3)默认值参数
定义函数时,可给每个形参指定默认值。非默认参数必须放在默认参数之前。
def printInfo(name, age=20):
print("姓名:", name)
print("年龄:", age)
printInfo("zhangsan")
printInfo("lisi", 30)
4)不定长参数
参数的个数是不确定的。
# *args 以元组形式接收
def printInfo(num, *vartuple):
print(num)
print(vartuple)
printInfo(70, 60, 50)
# **kwargs 以字典形式接收
def printInfo(num, **vardict):
print(num)
print(vardict)
printInfo(10, key1=20, key2=30)
注意:加了
*的参数会以元组形式导入,加了**的参数会以字典形式导入。
6.5.5 解包传参
若函数的形参是定长参数,可以通过 * 和 ** 对列表、元组、字典等解包传参。
def func(a, b, c):
return a + b + c
tuple11 = (1, 2, 3)
print(func(*tuple11))
# 字典中key的名称和参数名必须一致
dict1 = {"a": 1, "b": 2, "c": 3}
print(func(**dict1))
6.5.6 强制使用位置参数或关键字参数
/ 前的参数必须使用位置传参,* 后的参数必须用关键字传参。
def f(a, b, /, c, d, *, e, f):
print(a, b, c, d, e, f)
f(1, 2, 3, d=4, e=5, f=6)
6.5.7 防止函数修改列表
有时要函数对列表进行处理,又不希望函数修改原列表,可以使用 copy.deepcopy()。
import copy
def multiply2(var1):
var1[3].append(400)
print("函数内处理后:", var1)
list1 = [1, 2, 3, [100, 200, 300]]
print("函数外处理前:", list1)
multiply2(copy.deepcopy(list1))
print("函数外处理后:", list1)
6.6 函数说明文档
编写了函数说明文档后,可以通过 help(函数名) 获取函数说明文档。
def adult(age=18):
"""根据年龄判断是否成年"""
result = "未成年"[age >= 18:]
return result
help(adult)
6.7 返回值
返回值就是函数完成工作后,给调用者的一个结果。
- 在函数中使用
return关键字可以返回结果,并结束正在执行的函数。 - 如果
return后面跟表达式,在结束函数的同时向调用方返回一个表达式。 - 如果仅仅是
return关键字,后面没有加内容,函数返回 None。 - 如果函数中没有
return语句,在函数运行结束后也会返回 None。 return语句可以返回多个值,多个值会放在一个元组中。
def f(a, b, c):
return a, b, c, [a, b, c]
print(f(1, 2, 3)) # (1, 2, 3, [1, 2, 3])
6.8 函数嵌套调用
在一个函数中调用另一个函数,当内层函数执行完之后才会继续执行外层函数。
def function_A():
print("\t 函数 A 开始执行")
print("\t 函数 A 执行中...")
print("\t 函数 A 结束执行")
def function_B():
print("函数 B 开始执行")
print("函数 B 执行中...")
function_A()
print("函数 B 执行中...")
print("函数 B 结束执行")
function_B()
6.9 变量的作用域
Python 的作用域一共有 4 种:
- L(Local):局部作用域
- E(Enclosing):嵌套作用域(闭包函数外的函数中)
- G(Global):全局作用域
- B(Built-in):内建作用域
以 L → E → G → B 的规则查找。
注意:Python 中只有模块(module)、类(class)以及函数(def、lambda)才会引入新的作用域,其它的代码块(如 if/elif/else、try/except、for/while 等)不会引入新的作用域。
6.9.1 全局变量和局部变量
定义在函数内部的变量拥有局部作用域,定义在函数外的拥有全局作用域。
6.9.2 global 关键字
函数内使用 global 声明全局变量后,可以修改全局变量。
def function_a():
global var1
var1 = 200
print("var1:", var1)
var1 = 100
print(var1) # 100
function_a() # var1: 200
print(var1) # 200
当全局变量为可变类型时,函数内不使用
global声明,也可以对其进行修改。
6.9.3 nonlocal 关键字
nonlocal 用作内部作用域修改外部作用域(嵌套作用域)的变量。
def function_outer():
var1 = 1
print(var1)
def function_inner():
nonlocal var1
var1 = 200
function_inner()
print(var1)
function_outer() # 1 -> 200
6.10 递归
6.10.1 概念
递归是一种逻辑思想,将一个大工作分为逐渐减小的小工作。在程序中就是依靠函数嵌套这个特性来实现的。
6.10.2 本质
递归调用就是在函数体中又调用了函数本身。
6.10.3 确定递归的两个要点
- 确定它们之间的规律
- 确定递归结束的条件
6.10.4 递归案例:求一个整数n的阶乘
# 不使用递归的方式
def get_factorial(num):
res = 1
for n in range(1, num + 1):
res *= n
return res
print(get_factorial(5))
# 使用递归
def get_factorial2(n):
return n * get_factorial2(n - 1) if n > 1 else 1
print(get_factorial2(5)) # 120
6.11 匿名函数
6.11.1 语法
Python 使用 lambda 来定义匿名函数:
lambda 参数列表: 表达式
- lambda 只是一个表达式,函数体比 def 简单很多。
- lambda 的主体是一个表达式,而不是一个代码块。
- lambda 函数拥有自己的命名空间。
6.11.2 使用匿名函数传参
def function(a, b, operator):
return operator(a, b)
print(function(1, 2, lambda x, y: x + y))
6.11.3 匿名函数作为内置函数的参数
1)sorted()
student_list = [{"name": "zhang3", "age": 36}, {"name": "li4", "age": 14}, {"name": "wang5", "age": 27}]
print(sorted(student_list, key=lambda x: x["age"]))
2)map()
map_result = map(lambda x: x * x, [0, 1, 3, 7, 9])
print(list(map_result)) # [0, 1, 9, 49, 81]
3)filter()
filter_result = filter(lambda x: x >= 0, [-0, -1, -3, 7, 9])
print(list(filter_result)) # [0, 7, 9]
4)reduce()
from functools import reduce
reduce_result = reduce(lambda x, y: x * y, [1, 2, 3, 4, 5])
print(reduce_result) # 120
6.12 函数的注释(了解)
Python 3.x 引入了函数注释,以增强函数的注释功能:
def dog(name: str, age: (1, 99), species: '狗狗的品种') -> tuple:
return (name, age, species)
print(dog.__annotations__)
# {'name': <class 'str'>, 'age': (1, 99), 'species': '狗狗的品种', 'return': <class 'tuple'>}
可以使用 : 对参数逐个进行注释,返回值使用 -> 标注。所有的注释都会保存至函数的 __annotations__ 属性中。