一、函数是什么
计算机语言中的函数是类比于数学中的函数演变来的,但是又有所不同。前面的知识中我们学会了运用基础语法(列表、字典)和流程控制语句貌似也能处理一些复杂的问题,但是相对于相似的大量重复性的操作我们就没办法用之前的逻辑方法来解决了,这时候就需要一个可以概括这些重复性操作的统一代码来描述其特征来实现,所以函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。
定义:函数是值将一组语句集合通过一个名字(函数名)封装起来,要想执行这个函数,只需调用其函数名即可。
优点:在解决问题时,使用函数有诸多好处,最主要的好处就是:增强代码的重用性和可读性,后期扩展方便
二、函数的定义
2.1、格式
def 函数名(参数列表):
函数体
函数定义的简单规则:
函数代码块以 def 关键词开头,后接函数标识符名称和圆括号()。
任何传入参数和自变量必须放在圆括号中间。圆括号之间可以用于定义参数。
函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
函数内容以冒号起始,并且缩进。
return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None。
举个栗子,简单认识一下函数:
def greet_user():
"""显示最简单的问候语"""
print("Hello,"
greet_user()
上面这三行代码就是一个简单的函数实例,def是必不可少的,用于定义函数,greet_user是定义的函数名,() - 括号内指出函数为完成其任务需要什么样的信息,也就是参数,但是这里它不需要任何信息就能完成其工作,因此括号是空的(即便如此,括号也必不可少)。最后,定义以冒号结尾。
中间的缩进行构成了函数体。这里是函数的重点,所有的操作都是定义在这里的。上面的注释处的文本是被称为文档字符串 ( docstring )的注释,描述了函数是做什么的。文档字符串用三引号括起, Python 使用它们来生成有关程序中函数的文档。
greet_user() 这是对上面定义好的函数的调用。Python中要调用函数,可依次指定函数名以及用括号括起的必要信息-参数,上面的函数因为没有定义参数所以就不需要进行参数的传递,直接加括号调用。
2.2、函数的命名规则
函数名必须以下划线或字母开头,可以包含任意字母、数字或下划线的组合。不能使用任何的标点符号;
函数名是区分大小写的。
函数名不能是保留字。
三、参数的传递
3.1、形参和实参
举个栗子:
def greet_user(username):
print("Hello,"+username.title())
greet_user('jack')
上面的代码片段,定义函数greet_user,需要向函数中传递参数,在上面的函数中名后的括号中的username就是形参(形式参数),调用函数时给函数传入的值'jack'就是实参(实际参数)。实参将用户传递的值传递给形参,形参在传递进函数体中进行相关运算执行。
形参:形式参数,不是实际存在,是虚拟变量。在定义函数和函数体的时候使用形参,目的是在函数调用时接收实参(实参个数,类型应与实参一一对应)
实参:实际参数,调用函数时传给函数的参数,可以是常量,变量,表达式,函数,传给形参 。
区别:形参是虚拟的,不占用内存空间,形参变量只有在被调用时才分配内存单元,实参是一个变量,占用内存空间,数据传送单向,实参传给形参,不能形参传给实参。
3.2、位置实参
鉴于函数定义中可能包含多个形参,因此函数调用中也可能包含多个实参。向函数传递实参的方式很多,可使用位置实参 ,这要求实参的顺序与形参的顺序相同;也可使用关键字实参 ,其中每个实参都由变量名和值组成;还可使用列表和字典。
调用函数时, Python 必须将函数调用中的每个实参都关联到函数定义中的一个形参。为此,最简单的关联方式是基于实参的顺序。这种关联方式被称为位置实参。
文字描述太多不如举个栗子:看一个显示宠物信息的函数。这个函数指出一个宠物属于哪种动物以及它叫什么名字
def describe_pet(pet_name,animal_type):
print("\nI have a " + animal_type)
print("My " + animal_type + "'s name is " + pet_name.title())
describe_pet('Hellen','dog')
describe_pet('dog','Hellen') # 错误的位置参数传递
上述代码的运行结果为:
C:\Python37\python3.exe D:/pythoncode/Exercise/Exer8/Exer8-2.py
I have a dog
My dog's name is Hellen
# 参数位置传递错误 结果也不一样
I have a Hellen
My Hellen's name is Dog
Process finished with exit code 0
分析:上述代码上两次实参的传递因为位置不同而得到不同的结果,第二个显然不是我们想要的结果,所以在进行实参传递时确认函数调用中实参的顺序与函数定义中形参的顺序一致。
3.3、关键字实参
关键字参数是向形参传递--值对,关键字实参传递让我们无需考虑函数调用中的实参顺序,还清楚地指出了函数调用中各个值的用途。
举个栗子:
def describe_pet(pet_name,animal_type):
print("\nI have a " + animal_type)
print("My " + animal_type + "'s name is " + pet_name.title())
# 关键字传参不必考虑位置
describe_pet(pet_name='Hellen',animal_type="dog")
describe_pet(animal_type="dog",pet_name='Hellen')
上述代码的运行结果为:
C:\Python37\python3.exe D:/pythoncode/Exercise/Exer8/Exer8-2.py
I have a dog
My dog's name is Hellen
I have a dog
My dog's name is Hellen
Process finished with exit code 0
分析:上面的这个例子是关键字实参传递,使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。
注意:如果关键字参数和位置参数一起出现时,关键字参数是不能写在位置参数前面的
3.3、默认参数
编写函数时,可给每个形参指定 默认值 。在调用函数中给形参提供了实参时, Python 将使用指定的实参值;否则,将使用形参的默认值。因此,给形参指定默认值后,可在函数调用中省略相应的实参。使用默认值可简化函数调用,还可清楚地指出函数的典型用法。
举个栗子:
def describe_pet(pet_name,animal_type='dog'):
print("\nI have a " + animal_type)
print("My " + animal_type + "'s name is " + pet_name.title())
describe_pet('jack') # 默认animal_type='dog'
describe_pet('pick','cat') # 使用位置实参,赋值animal_type='cat'
describe_pet(animal_type="cat",pet_name='Hellen') # 关键字传参
上述代码的执行结果为:
C:\Python37\python3.exe D:/pythoncode/Exercise/Exer8/Exer8-2.py
I have a dog
My dog's name is Jack
I have a cat
My cat's name is Pick
I have a cat
My cat's name is Hellen
Process finished with exit code 0
分析:这里修改了函数 describe_pet() 的定义,在其中给形参 animal_type 指定了默认值 'dog' 。这样,调用这个函数时,如果没有给 animal_type 指定值, Python 将把这个形参设置为 'dog' ,如果指定 animal_type的值则就使用指定的值。
3.4、不定长参数
有时候可能我们预先不知道函数需要接受多少个实参,好在 Python 允许函数从调用语句中收集任意数量的实参。
举个栗子:
def make_pizza(*topings):
print(topings)
make_pizza('pepperoni')
make_pizza('mushrooms','green peppers','extra cheese')
上述代码的执行结果为:
C:\Python37\python3.exe D:/pythoncode/Exercise/Exer8/Exer8-5.py
('pepperoni',)
('mushrooms', 'green peppers', 'extra cheese')
Process finished with exit code 0
分析:上面的make_pizza函数只有一个形参 *toppings ,但不管调用语句提供了多少实参,这个形参都将它们统统收入囊中。**形参名 *toppings 中的星号让 Python 创建一个名为 toppings 的空元组,并将收到的所有值都封装到这个元组中**,即使是一个值也会生成一个元组。
注意:上面make_pizza函数还有另一种传值方式,test(*[1,2,3,4,5]),这样还是会以元组的方式返回数据
其中 toping = [1,2,3,4,5]
举个栗子:
def make_pizza(*topings):
print(topings)
make_pizza(*'list') ====> ('l', 'i', 's', 't') /传入字符串时返回元组
make_pizza(*(1,2,3)) ====> (1, 2, 3) /传入元组时返回元组
make_pizza(*[1,2,3]) ====> (1, 2, 3) /传入列表时返回元组
make_pizza(*{'first':'name','last':'name2'}) ====> ('first', 'last') /传入字典时,只返回key值元组
3.4.1、结合使用位置实参和任意数量实参
如果要让函数接受不同类型的实参,必须在函数定义中将接纳任意数量实参的形参放在最后。 Python 先匹配位置实参和关键字实参,再将余下的实参都收集到最后一个形参中。
def make_pizza(size,*topings):
print(size,topings)
make_pizza(2,'pepperoni')
make_pizza(16,23,45,'mushrooms','green peppers','extra cheese')
上述代码的执行结果为:
C:\Python37\python3.exe D:/pythoncode/Exercise/Exer8/Exer8-5.py
2 ('pepperoni',)
16 (23,45,'mushrooms', 'green peppers', 'extra cheese')
Process finished with exit code 0
分析:基于上述函数定义, Python 将收到的第一个值存储在形参 size 中,并将其他的所有值都存储在元组 toppings 中。
**注意:*topings只接收位置参数,不能接收关键字参数**
当位置参数遇到topings时:就是有位置参数同时也有N个实参传入,首先将值赋给位置参数,然后剩下的多余的值赋给args以元组的形式输出
3.4.2、使用任意数量的关键字实参
有时候,需要接受任意数量的实参,但预先不知道传递给函数的会是什么样的信息。在这种情况下,可将函数编写成能够接受任意数量的 键—值 对 —— 调用语句提供了多少就接受多少。
举个栗子:
def build_profile(first, last, **user_info):
profile = {}
profile['first_name'] = first
profile['last_name'] = last
for key, value in user_info.items():
profile[key] = value
return profile
user_profile = build_profile('albert','eniston',
location = 'China',
field = 'physics',
age = 18)
print(user_profile)
上述代码的执行结果为:
C:\Python37\python3.exe D:/pythoncode/Exercise/Exer8/Exer8-5.py
{'first_name': 'albert', 'last_name': 'eniston', 'location': 'China', 'field': 'physics', 'age': 18}
Process finished with exit code 0
分析:形参 * *user_info 中的两个星号让 Python 创建一个名为 user_info 的空字典,并将收到的所有名称 — 值对都封装到这个字典中。上面的build_profile函数不知道用户会输入多少信息,指明姓名后,再提供住址、年龄等信息,函数都会将这些信息都存储在空字典中。
注意:上述任意数量关键字实参传值时也有两种方法:一是使用键值传值,二是直接调用**{字典}的方式
print(build_profile(**{'first':123,'last':'name'})) # 也接受**{字典} 的传值方式
# 运行结果
{'first_name': 123, 'last_name': 'name'}
3.4.2.1、当位置参数遇上**kwargs时:
def build_profile(name, **user_info): #name 是一个位置形参 **user_info是用来生成一个空字典接受其他变量
print(name)
print(user_info)
# 首先是赋值一个位置实参,然后将后面键值对指定的实参存储到字典里
build_profile('keitter',age=12,location = 'China')
# 使用特殊传值方式 **{} 传入的是字典 最后传出的也是字典
build_profile('\njson',**{'first':'jack','location':'China'})
# 当只传入一个位置实参时 则后面的**user_info 还是会生成一个空字典
build_profile('\nkitter')
# 当传入的位置参数个数和形参中定义好的位置参数数量不符时就直接报错
build_profile('\nkitter','name',age=12,location = 'China') ===》直接报错
C:\Python37\python3.exe D:/pythoncode/Exercise/Exer8/Exer8-5.py
keitter
{'age': 12, 'location': 'China'}
json
{'first': 'jack', 'location': 'China'}
kitter
{}
Process finished with exit code 0
3.4.2.2、当位置参数、默认参数遇到 **kwargs 时:
注意:默认参数一定不能放到 **kwargs 后面,否则程序会直接报错。
举个栗子:
def build_profile(name, age=18,**user_info): #name 是一个位置形参 age是默认形参 **user_info是用来生成一个空字典接受其他变量
print(name)
print(age)
print(user_info)
# 这很好理解,就是位置实参传值,将默认参数age赋值为12,然后剩下的参数生成字典
build_profile('keitter',12,location = 'China')
# 这和上面那个差不多,只不过是使用了关键字实参给默认参数赋值
build_profile('\nHellon',location = 'China',age=23)
# 使用特殊传值方式 **{} age值和上面两个一样,可以是位置实参,也可以关键字指定
build_profile('\njson',**{'first':'jack','location':'China'})
# 当只传入一个位置实参时 则会生成默认参数值和后面的**user_info 生成的一个空字典
build_profile('\nkitter')
C:\Python37\python3.exe D:/pythoncode/Exercise/Exer8/Exer8-5.py
keitter
12
{'location': 'China'}
Hellon
23
{'location': 'China'}
json
18
{'first': 'jack', 'location': 'China'}
kitter
18
{}
Process finished with exit code 0
3.4.2.3、当位置参数、默认参数遇上 *args 和 **kwargs 时
注意: *args 只接收位置参数,转换成元组的形式,不接收关键字参数
* *kwargs 只接收关键字参数,转换成字典的形式
**位置顺序不能变, args必须放在 *kwargs 前面 ,位置参数一定是放在默认参数前面,不定长参数肯定是放在最后面。**
举个栗子:
def build_profile(name, age=18, *user, **user_info): # 位置参数 默认参数 两种不定长参数
print(name)
print(age)
print(user)
print(user_info)
# 位置参数肯定是要匹配的 默认参数可给可不给 *args 只接收位置参数 **kwargs 只接收关键字参数
build_profile('keitter',12,'people',location = 'China')
# 多种形参混合时,不能将默认实参放到最后 会直接报错
build_profile('\nHellon','people',location = 'China',age=23) ====》 直接报错
# 特殊参数传递方法 注意在传递*[]时,没有指定默认实参时,会将第一个值赋给默认参数
build_profile('\njson',*[{'name':'jack'},1,2,3],**{'first':'jack','location':'China'},)
# 默认没有给 *args 和 * *kwargs 传值时,还是会生成一个空列表和空字典
build_profile('\nkitter','people')
C:\Python37\python3.exe D:/pythoncode/Exercise/Exer8/Exer8-5.py
keitter
12
('people',)
{'location': 'China'}
json
{'name': 'jack'} # 注意在传递*[]时,没有指定默认实参时,会将第一个值赋给默认参数
(1, 2, 3) # 注意在传递*[]时,除了传给默认参数的,后面的剩的值还是会生成一个元组
{'first': 'jack', 'location': 'China'}
kitter
people
() # 还是会生成一个空列表和空字典
{}
Process finished with exit code 0
四、返回值
函数并非总是直接显示输出,相反,它可以处理一些数据,并返回一个或一组值。函数返回的值被称为 返回值 。在函数中,可使用 return 语句将值返回到调用函数的代码行。返回值让你能够将程序的大部分繁重工作移到函数中去完成,从而简化主程序。
返回值的分类:
当返回值数 = 0 ==》返回none
当返回值数 = 1 ==》返回object,是什么就返回什么
当返回值数 > 1 ==》 返回元组(tuple)元组中包含所有的返回值
举个栗子:
def get_formatted(first_name,last_name):
full_name = first_name + ' ' + last_name
return full_name.title()
musician = get_formatted('qi','siwill')
print(musician)
上述代码的执行结果为:
C:\Python37\python3.exe D:/pythoncode/Exercise/Exer8/Exer8-3.py
Qi Siwill
Process finished with exit code 0
分析:调用返回值的函数时,需要提供一个变量,用于存储返回的值。在这里,将返回值存储在了变量 musician 中
函数可返回任何类型的值,包括列表和字典等较复杂的数据结构
举个返回字典的例子:
def build_people(first_name,last_name):
person = {'first':first_name,'last':last_name}
return person
musician = build_people('jack','rose')
print(musician)
上述代码的执行结果为:
C:\Python37\python3.exe D:/pythoncode/Exercise/Exer8/Exer8-3.py
{'first': 'jack', 'last': 'rose'}
Process finished with exit code 0
分析:上述代码就是指返回值个数为1 ,那么是什么就返回什么,person是一个字典所以就返回一个字典,如果是其他对象也都返回对应的。
再举个栗子:
def build_people(first_name,last_name):
# person = {'first':first_name,'last':last_name}
return first_name,last_name
musician = build_people('jack','rose')
musician2 = build_people('jack','["Rose","jack"]')
print(musician)
print(musician2)
C:\Python37\python3.exe D:/pythoncode/Exercise/Exer8/Exer8-3.py
('jack', 'rose')
('jack', '["Rose","jack"]')
Process finished with exit code 0
分析:上述代码就是指返回值个数大于1 ,那么则返回一个元组,包含所有值的元组。