总体来说,函数的两个主要目的是:降低编程难度和实现代码复用。 函数是一种功能抽象,复用它可以将一个复杂的大问题分解成一系列简单 的小问题,同时,小问题还可以继续划分成更小的问题,是一种分而治之 的思想应用。当每个小问题都细化到足够简单时,为每个小问题编写程 序,并通过函数封装,由小问题的解决到整个大问题的解决。这就是一种 自顶向下的程序设计思想。
一. 函数定义
def 函数名(参数1,参数2,...):
函数体
return 返回值列表
函数定义
- def:关键字,标志着函数的开始;
- 函数名: 函数唯一的标识,命名方式遵循
变量的命名规则
;- 参数:参数列表中的参数是形式参数,是调用该函数时传递给它的值,可以是0个,也可以是一个或多个。当传递多个参数时,各参数由逗号分隔。没有参数也需要保留括号。形参只在函数体中有效。
- 函数体: 冒号:用于标记函数体的开始。函数每次被调用时执行的代码,由一行或多行代码组成。
- return:
- 标志函数的结束,将返回值赋给函数的调用者。
- 若是没有返回值,则无须保留return语句,在函数体结束位置将控制权返回给调用者。
二. 函数调用
调用时,参数列表中给出实际要传入函数内部的参数,这类参数称为实际参数,即“实参”。实参可以是变量、常量、表达式、函数等。
在程序执行过程中,调用函数其实分成了4个步骤:
- 调用程序在调用处暂停执行。
- 在调用时将实参复制给函数的形参。
- 执行函数体语句。
- 函数结束时给出返回值,程序回到调用前暂停处继续执行。
- 主程序先按顺序执行到c=fact(n)/(fact(m)∗fact(n-m)) 时,暂停,转到函数fact()。
- 将实参n
复制
后传递给形参a。- 执行函数fact()中的语句。
- 函数执行结束时,得到返回值f,回到主程序c=fact(n)/ (fact(m)∗fact(n-m)),得到了fact(n)的值。
- 以同样的方式再次暂停,调用函数求得fact(m)、fact(n-m)的值。
- 回到主程序c=fact(n)/(fact(m)∗fact(n-m)),继续往下执 行print©。
三. 向函数传参
1. 位置传参
def fmax(a, b):
return max(a, b)
fmax(1, 2) # 1赋给a,2赋给b
2. 关键字传参
关键字实参是传递给函数的名称值对。关键字实参让你无须考虑函数调用中的实参顺序,还清楚地指出了函数调用中各个值的用途。
def describe_pet(animal_type, pet_name):
"""显示宠物的信息。"""
print(f"\nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
if __name__ == '__main__':
# 关键字实参的顺序无关紧要,因为Python知道**各个值该赋给哪个形参**。下面两个函数调用是等效的:
describe_pet(animal_type='hamster', pet_name='harry')
describe_pet(pet_name='harry', animal_type='hamster')
3. 参数默认值
编写函数时,可给每个形参指定默认值。
如果你发现调用describe_pet()时,描述的大多是小狗,就可将形参animal_type的默认值设置为’dog’。这样,调用describe_pet()来描述小狗时,就可不提供这种信息:
def describe_pet(pet_name, animal_type='dog'):
"""显示宠物的信息。"""
print(f"\nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
if __name__ == '__main__':
describe_pet(pet_name='willie')
# I have a dog.
# My dog's name is Willie.
# 虽然给animal_type指定了默认值,但py依然将这个参数视为位置参数。所以如果函数调用中只包含宠物的名字,这个实参将关联到函数定义的形参。所以这就是需要将pet_name放在形参列表开头的原因。
describe_pet('willie')
4. 包裹传递
- 又称不定参数传递,适用于不知道函数要接收多少个参数的情况。
- 传递的参数需要在位置传递和默认值传递之后。包括:*args(接收元组),**kwargs(关键字的map)
def fmax(*args):
return max(args)
fmax(1, 2, 3) # args被解释为一个元组,返回其中最大的数3
def fmax(**kwargs):
return max(kwargs.values())
fmax(a=1, b=2, c=3) # 返回3
5. 解包裹传递
解包裹传递是让容器里面的元素与多个参数一一对应。
- 使用
*
解包序列(元组、列表均可),使其元素称为独立的参数位置参数: - 使用
**
解包字典,使其元素称为独立的关键字参数。
如下前两种比较常用,第三种不常用:
def fmax(a,b,c,d):
return max(a,b,c,d)
numbers=[1,3,2,4]
fmax(*numbers) # numbers列表解包分别传递给a,b,c,d
def fmax(a,b,c,d):
return max(a,b,c,d)
values={'a':1,'b':3,'c':2,'d':4}
fmax(**values) # 相当于fmax(a=1,b=3,c=2,d=4)
def func(*args):
print(args)
args = (1,3,4)
func(*args) #(1, 3, 4) #解包,所以元组有三个元素
func(args) #((1, 3, 4),) #没有解包,所以元组只有一个元素
6. 混合传递
# 1. 混合使用位置传递、默认值传递和可变位置参数
def fmax(a, b=10, *args):
numbers = (a, b) + args
return max(numbers)
fmax(3) # a=3,b使用默认值10,没有其他参数,返回10
fmax(3, 4, 6, 8, 1) # a=3,b=4,args=(6,8,1),返回8
# 2. 混合使用位置传递、默认值传递、可变位置参数和可变关键字参数
def fmax(a, b=10, *args, **kwargs):
# 接收map
numbers = (a, b) + args + tuple(kwargs.values())
return max(numbers)
fmax(3, 4, 2, 9, x=5, y=7) # a=3,b=4,args=(2,9),kwargs={'x':5,'y':7},返回9
fmax(3, x=11, y=6) # a=3,b使用默认值10,没有位置参数,kwargs={'x':11,'y':6},返回11
如果要让函数接受不同类型的实参
,必须将接纳任意数量实参的形参放在最后。
def make_pizza(size, *toppings):
"""概述要制作的比萨。"""
print(f"\nMaking a {size}-inch pizza with the following toppings:")
for topping in toppings:
print(f"- {topping}")
if __name__ == '__main__':
make_pizza(16, 'pepperoni')
make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')
注意:你经常会看到通用形参名*args,它也收集任意数量的位置实参。
四. 返回值
return是
可选项
,它的作用是结束并退出当前函数,将程序返回到函数被调用时的位置继续执行,同时将函数中的数据返回给主程序。
return语句可以同时
返回多个结果
给函数调用处的变量。当存在多个返回值时,会形成一个元组。
def mult(n):
s = 0
m = 1
for i in range(1, n + 1):
s += i
m *= i
return s, m
sum, mul = mult(5)
print('累加之和:{},阶乘之积:{}'.format(sum, mul))
t = mult(6)
print(type(t))
# 累加之和:15,阶乘之积:120
# <class 'tuple'>
返回值有两个,可以用两个变量接收。
五. 变量作用域
Python中的变量不是在哪个位置都可以访问的,具体的访问权限取决于定义变量的位置。变量所处的有效范围称为变量的作用域。
根据变量作用域的不同,可以将变量分为两类:全局变量和局部变量。
- 全局变量是指在
函数之外定义
的变量,一般没有缩进,在程序执行的全过程
有效。- 局部变量是指在函数内部定义的变量,
- 仅在函数内部有效,一旦退出函数,变量就不再有效。
- 想要在函数中使用全局变量,一般会使用global声明。
简单数据类型变量在主程序和函数体中重名.py
n = 1
def fun(a, b):
n = a * b
return a + b
s = fun(10, 12)
print(s, n)
简单数据类型在函数体中使用global声明.py
n = 1
def fun(a, b):
global n
n = a * b
return a + b
s = fun(10, 12)
print(s, n)
组合数据类型在主程序中创建,在函数体中使用.py
ls = []
def fun(a, b):
ls.append(a * b)
return a + b
s = fun(10, 12)
print(s, ls)
组合数据类型在主程序中创建,在函数体中再创建并使用.py
ls = []
def fun(a, b):
ls = []
ls.append(a * b)
return a + b
s = fun(10, 12)
# ls返回为空
print(s, ls)
由此,可以总结一下Python函数对变量的作用要遵守的原则:
- 简单数据类型变量在使用global保留字声明后,作为全局变量使用,函数退出后,该变量仍被保留,且数值被函数改变。
- 如果函数内部真实地创建了组合数据类型变量,无论是否与全局变量同名,
函数仅对内部的局部变量进行操作
,函数退出后局部变量被释放,而全局变量的值不受函数影响。