1. 函数的基础概念
一般情况下,在不同地方被重复使用了多次的代码,建议通过函数(function)进行优化,使用 def 关键字定义函数。
调用:函数定义后默认不会执行,只有被调用时才会执行内部代码。函数的定义一定得在调用之前。
# 定义函数
def draw_square():
print("画一个正方形")
# 调用函数
draw_square()
函数的分类
- 内置函数:Python 系统自带的,如
type(),max(),len()等。 - 自定义函数:开发者自己编写的。注意不要和内置函数重名。
2. 参数 (Parameter)
参数的作用是将外面的数据传递到函数内部进行使用,让函数的功能更加灵活。
- 形参 (Parameter):定义函数时用来接收数据的变量。
- 实参 (Argument):调用函数时实际传递的数据。
- 如果有多个参数,使用逗号
,隔开。调用时每一个形参都必须有明确的实参。
def draw_square(side): # side 是形参
print(f"画一个边长为 {side} 的正方形")
draw_square(50) # 50 是实参
2.1 参数的默认值
如果某个参数经常是固定值,可以给它设置默认值,这样调用时就可以省略该实参。
限制:带默认值的参数必须放在形参列表的最右边,也就是从右往左必须是连续的。
为什么会有上面的限制呢? 你要中间参数搞一个默认值,我在调用的时间,传过去的参数,方法都不知道怎么一一对应了。
def hello(name, times=1):
for _ in range(times):
print(f'Hi, {name}!')
hello('小码哥') # 默认 times=1
hello('MJ', 3) # 传入 times=3
2.2 关键字参数
默认是从左往右按顺序传参(位置参数)。也可以按名字传递(关键字参数)。 好处是可读性高,且不需要死记顺序。
def move_circle(x, y, radius):
pass
move_circle(50, 60, 70) # 位置参数
move_circle(radius=70, x=50, y=60) # 关键字参数,无视顺序
3. 返回值 (Return Value)
根据有无返回值,函数可分为:
- 有返回值的函数:执行完毕会返回具体数据(如
max,sum)。 - 无返回值的函数:执行完毕不返回具体数据(如
print),相当于默认返回了None。
return 关键字
作用有两点:
- 返回函数的结果。
- 立即终止函数的运行(遇到
return函数就结束了)。
def odd_sum(n):
return sum([i for i in range(1, n + 1, 2)])
同时返回多个数据
可以通过返回元组来实现返回多个数据。
def odd_even_sum(n):
o_sum = sum([i for i in range(1, n, 2)])
e_sum = sum([i for i in range(2, n, 2)])
return o_sum, e_sum # 实际上返回的是一个元组 (o_sum, e_sum)
odd, even = odd_even_sum(10) # 解包获取
4. 空语句 pass
pass 相当于空代码,啥也不干。如果函数体还没想好怎么写,可以先用 pass 占位,防止语法报错。
def sleep():
pass
5. 可变参数与参数解包
5.1 可变参数 * (打包为元组)
记住 * 用于接收任意数量的位置参数,可变参数的作用就是说我的参数个数不确定,方法在调用的时间将可变参数打包成一个元祖
def hello(*names):
for n in names:
print(f'Hi, {n}!')
hello('MJ', 'Jack', 'Rose') # 内部 names 是 ('MJ', 'Jack', 'Rose')
注意:可变参数右边的形参,如果想传值,必须使用关键字参数!且可变参数不能设置默认值。
hello('MJ','Jack','Title', title='哈罗')
上面的 title参数必须这么写成title='哈罗'
5.2 关键字可变参数 ** (打包为字典)
** 用于接收任意数量的关键字参数。可以理解为 * 打包参数打包成元祖,两个** 打包成字典
def show_info(**info):
if 'name' in info:
print('姓名 =', info['name'])
show_info(city='广州', name='MJ', age=18)
# 内部 info 是 {'city': '广州', 'name': 'MJ', 'age': 18}
* 和 ** 混用时,* 形参必须放在 ** 形参的左边!
5.3 强制使用关键字参数
在形参中写一个单独的 *,那么它右边的参数必须全部强制使用关键字传参。其实还是前面说的,可变参数后面的其他参数,在调用的时间必须指名名字,要不函数对不上谁应该传给谁
def move_circle(*, x=0, y=0, radius=50):
pass
# move_circle(10, 20, 30) # 报错
move_circle(x=10, y=20, radius=30) # 必须这样写
5.4 函数调用时的解包实参 (* 和 **)
上面说的是方法定义的时间使用 * 和 ** ,这里指的是在方法调用的时间,在调用的时候* 解包可迭代对象(列表、元组、集合)当做位置实参。** 解包字典对象当做关键字实参。
def move_circle(x, y, radius):
pass
move_circle(*[50, 60, 70]) # 等价于 move_circle(50, 60, 70)
d = {'x': 10, 'y': 20, 'radius': 30}
move_circle(**d) # 等价于 move_circle(x=10, y=20, radius=30)
6. 引用传递 (Pass-by-reference)
Python 将实参传递给形参时,使用的是引用传递(实参和形参指向同一个内存对象)。
后果:
如果传递的是可变类型(如列表),在函数内部使用方法(如 append)或者索引赋值修改了该对象,外部的变量也会跟着改变。
但如果是重新给形参赋值(如 a = [1, 2]),则不会影响外部变量。
def test(a):
a.append(66) # 外部列表跟着变
s = [11, 22]
test(s)
print(s) # [11, 22, 66]
7. 类型标注 (Type Annotation)与文档注释
Python 函数可以接收任意类型参数,为了防止传错类型,可以使用类型标注提高可读性并获得编辑器的语法提示(但它仅仅是注释,对执行没有实质约束力)。
def avg(a: float, b: float) -> float:
"""
求a和b的平均值
:param a: 第1个数值
:param b: 第2个数值
:return: 平均值
"""
return (a + b) / 2
# 查看文档和标注
print(avg.__doc__) # 打印文档字符串
print(avg.__annotations__) # 打印类型标注字典
8. 作用域 (Scope) 与 global/nonlocal
根据变量定义位置的不同,其作用范围分为:
- 局部 (Local):定义在函数内部,只能在函数内部使用。
- 全局 (Global):定义在函数外部,整个 Python 文件都可使用。
- 内置 (Built-in):Python 系统内置。 搜索顺序(就近原则):局部 -> 全局 -> 内置。
global 关键字
如果在函数中要修改全局变量的值,需要先用 global 声明。
k = 10
def test():
global k
k = 20 # 这样就会真正修改外部的 k
内部函数与 nonlocal 关键字
函数里面还可以再嵌套定义函数。
如果在内部函数中要修改外部函数的局部变量,需要用 nonlocal 声明。
def test1():
n = 20
def test2():
nonlocal n
n = 200 # 这样就会修改 test1 里的 n
test2()