1.函数
函数:通过专门的代码组织,用来实现特定功能的代码段,具有相当独立性,可供其他代码重复调用。
2.函数定义基本语法
def 函数名([参数])
函数体
[return 返回值]
带参数的函数更加灵活、实用。可以通过改变参数来实现一系列的函数运算。带返回值的函数在执行return语句执行返回值后,将终止函数的执行,因此return语句之后的代码将不会被执行。
3.自定义函数的完善
(1)建立函数文档
用一对三个单引号来包括描述文档,在用help函数来获取自定义函数信息时可显示此描述信息。
(2)判断参数值是否符合要求
在函数体中添加对于参数值的if判断语句,比如利用type函数对参数类型进行判断,当不满足要求时,print传递值类型出错的语句,并return终止函数运行。
if type(nums)!=int:
print(‘输入值类型出错,必须是整数!’)
return
4.将函数放入模块中以被调用
自定义函数通过建立独立的函数模块文件(以.py为扩展名的文件),来共享给其他代码文件调用。
(1)建立函数模块
新建一个空白的代码文件,将自定义函数的代码复制到函数模块文件上。若有多个自定义函数,按顺序依次复制即可。
(2)调用函数模块
除内置函数外,其他函数的调用,必须先通过import语句进行导入才能调用。
1> import语句导入整个函数模块
import 函数模块名 #导入函数模块
模块名.函数名 #调用模块文件里的函数
eg.1
import test_function #导入模块
print(test_function.find_factor(8)) #调用自定义函数
2> import语句导入指定函数
from 模块名 import 函数名1[,函数名2,…]
eg.2
from test_function import find_factor #导入指定函数
print(find_factor(8)) #直接调用函数
3> import语句导入所有函数
from 模块名 import *
. *代表指定模块文件中的所有函数
eg.3
from test_function import *
4>利用as给模块、函数定义别名
模块名[函数名] as 别名
eg.4
import test_function as t1 #设置模块名别名为t1
t1.find_factor(8) #用别名代替模块名来调用函数from test_function import find_factor as f1 #设置函数别名为f1
f1(8) #用函数别名执行函数
as语句除了解决名称过长的问题外,还可解决函数名称发生冲突的问题。
注:在使用IDLE解释器用交互方式直接调用自定义函数之前,需要先运行对应的模块文件,才能被正确导入。
(3)模块搜索路径
用sys.path指定特定搜索模块路径
eg.5
import sys #导入IDLE自带sys模块
sys.path[0]='D:\python\\function #增加指定读取路径
注:由于\f为换页转义字符,为了避免出错,需在\f前再加一个\保证能够原样输出。否则输出为D:\python\x0cunction
5.参数的变化
自定义函数传递参数值时,有时需要一次传递多个参数,有时需要传递不确定个数的参数,有时需要传递参数为默认值。
(1)位置参数
传递的参数必须与函数定义的参数位置一一对应。
eg.1
def test1(name,age):
print(‘姓名%s,年龄%s’%(name,str(age)))
此时,调用函数时,参数值必须对应上。比如test1(‘Tom’,11),而test1(11,‘Tom’)就是错误的。
(2)关键字参数
为了避免传递值出错,调用函数时采用“参数名=值”的方式,此时无需考虑位置顺序。当部分指定时,需从右边开始指定,最左边一个可以不指定。
eg.2
test1(name=‘Tom’,age=20) #用“参数名=值”的方式指定参数
test1(age=20,name=‘Tom’) #可不考虑位置顺序
test1(‘Tom’,age=20)#右边指定,左边可以不指定
test1(name=‘Tom’,20)#左边指定,右边没指定,将出错
(3)默认值
预先设定默认值,当没有给该参数传值时,自动选择默认值。
eg.3
def test1(name=’’,age=20):
print(‘姓名%s,年龄%s’%(name,str(age)))
test1(18)
姓名18,年龄20 #默认给第一个参数赋值
test1()
姓名,年龄20#全部回复默认值
在编写自定义函数时,允许右边有默认值,左边没有,反之不行。
eg.4
def test1(name=’’,age): #此时,左边参数有默认值,右边没有,会报错
print(‘姓名%s,年龄%s’%(name,str(age)))
默认值要么全设,要么从右往左依次设
(4)不定长参数*、**
函数调用时,可以根据实际情况传递值的数量。
1>传递任意数量的参数值
函数名([参数1,参数2,…,]*参数n)
带*的参数n,可以接收任意数量的值,但一个自定义函数中只能有一个参数n,且必须放在最右边。
eg1.
def watermelon(name,*attributes):
description=’’
for get_t in attributes:description+=’’+get_t
print(description)
watermelon(‘西瓜’,‘甜’,‘圆形’,‘绿色’)
执行结果为:
西瓜
甜 圆形 绿色
2>传递任意数量的键值对
函数名([参数1,参数2,…,]**参数n)
与上类似,区别为:传递的是键值对。
eg.2
def watermelon(name,**attributes):
print(name)
return attributes #attributes参数实际为字典类型print(watermelon(‘西瓜’,taste=‘甜’,shape=‘圆形’,color=‘绿色’))
执行结果为:
西瓜
{‘taste’:‘甜’,‘shape’:‘圆形’,‘color’:‘绿色’}
6.传递元组、列表、字典值
自定义函数除了传递字符串、数字、键值外,还可以传递元组、列表、字典值。
(1)传递元组
eg.1
def watermelon(name,attributes):
print(name)
print(type(atributes))
return attibuteget_t=watermelon(‘西瓜’,(‘甜’,‘圆形’,‘绿色’))
print(get_t)
执行结果为:
西瓜
<class ‘tuple’>
(‘甜’,‘圆形’,‘绿色’)
(2)传递列表
eg.2
直接用eg.1中的自定义函数
get_L=watermelon(‘西瓜’,[‘甜’,‘圆形’,‘绿色’])
print(get_L)
执行结果为:
西瓜
<class ‘list’>
[‘甜’,‘圆形’,‘绿色’]
(3)传递字典
eg.3
get_D=watermelon(‘西瓜’,{‘taste’:‘甜’,‘shape’:‘圆形’,‘color’:‘绿色’})
print(get_D)
执行结果为:
西瓜
<class ‘dict’>
{‘taste’:‘甜’,‘shape’:‘圆形’,‘color’:‘绿色’}
在上例传递元组、列表、字典的过程中,函数watermelon没有发生变化, 有固定参数attitudes实现传递。
(4)传递列表、字典后的问题
由于列表、字典对象为可变对象,在函数内部对它们的元素进行变动后,会同步影响函数外部传递前的变量的元素。
eg.4
def Editfrult(name,attributes):
attributes[0]=attributes[0]*0.9 #在函数内部修改列表元素
return attributes #返回修改后的列表对象A=[20,‘甜’,‘圆形’,‘绿色’] #初始的列表
B=Editfrult(‘西瓜’,A) #调用Editfrult函数,并返回修改后的列表
print(A)
print(B)
执行结果为:
[18,‘甜’,‘圆形’,‘绿色’] #函数外的列表对象
[18,‘甜’,‘圆形’,‘绿色’] #修改后的列表对象
在自定义函数内修改列表对象导致函数外列表对象同步变化,证明传递列表(或字典)其实传递的是同一个内存地址的对象。为了不使函数外的列表(或字典)发生变化,在调用函数时可以采用copy列表的方式,如下。
eg.5
A=[20,‘甜’,‘圆形’,‘绿色’] #初始的列表
B=Editfrult(‘西瓜’,A.copy()) #实现列表复制
print(A)
print(B)
执行结果为:
[20,‘甜’,‘圆形’,‘绿色’] #函数体外的列表对象没有变化
[18,‘甜’,‘圆形’,‘绿色’] #修改后的列表对象
(5)函数传递对象总结
传递的对象可分为不可变对象、可变对象。数字、字符串、元组属于不可变对象;列表、字典属于可变对象。
不可变对象在函数里进行修改,会变成新的对象,在内存里产生新的地址。
可变对象在函数里进行修改,函数内外还是同一个对象,但是值会同步发生变化。
7.函数与变量作用域
只有清楚变量的作业范围,代码才不会逻辑混乱。
(1)全局变量与局部变量
根据变量可供访问的作用范围,可分为全局变量和局部变量。
全局变量自赋值定义开始,后续代码都可以访问该变量;局部变量只能在被定义的函数内部被访问。
(2)global关键字
函数内部默认只能读取全局变量的值,若要在函数内修改全局变量,则需要使用global关键字进行事先声明,否则直接修改全局变量会报错。
(3)闭包(Closure)
闭包是介于全局变量和局部变量之间的一种特殊变量。闭包变量定义位置在外部函数与内部嵌套函数之间。
(4)nonlocal关键字
要修改闭包变量需要事先用nonlocal关键字声明,才能对其进行修改操作。不鼓励使用该方法传递及修改值。
8.匿名函数
使用lambda来创建匿名函数,与def关键字定义函数相比,没有函数名称。
lambda[参数1,参数2,…]:expression
其中,expression表达式实现匿名函数的功能,并返回操作结果,通常具有函数return的功能。整个匿名函数在一行内实现所有定义。
a=lambda x,y:x*y
a(2,3)
执行结果为:
6
9.递归函数
递归是指一种通过重复将问题分解为同类的子问题而解决问题的方法。
“重复”即凡是通过循环语句可实现的,都可通过递归来实现。
“将问题分解为同类的子问题”,如持续循环的运算操作、持续循环的判断操作等,每次循环都是同样的一个“动作”,这个动作就是一个子问题。
利用函数实现递归算法的过程就是递归函数通过自己调用自己实现递归算法。
(1)使用递归函数
eg.1 求1,2,3,…,n加法和
def recursion_sum(num):
if num==1:
return num
return recursion_sum(num-1)+sum #调用递归函数,自己调用自己,重复动作:两个相邻数相加
print(recursion_sum(4))
执行结果为:
10
(2)递归函数在内存中的运行原理
总体实现思想:递归一次,在内存中开辟一个新的地址空间,记录递归过程状态 (中间值存放在新开辟的地址空间)。 一直递归分解到最小范围,最后得出要么找到对应的值,要么返回找不到的结果。
递归算法分为缩小范围和求值结果的层层返回两大步骤。这其实是在调用**栈(stack)**的进栈、出栈过程。每递归调用自己一次,就进栈一次,并在栈列表里记录调用的内容;每返回一次,就出栈,弹出值,并把值返回到上一个栈列表里,最后返回所求最终答案。
在调用递归函数开始,先入栈:先进行入栈操作,先调用的先进入,并在内存里开辟地址空间,记录调用过程;后出栈:后进行出栈操作,最上面的(后进入的)先出栈,并返回一个值给下一个(上一层),然后再第二个出栈,并返回值给下一个,以此类推。最后把值返回给函数调用处。
(3)利用递归函数实现二分法查找
eg.2 实现二分法查找
执行结果为:
注:在编写代码时,一定要注意if语句的缩紧格式。防止逻辑出错。 如下图中,第四个if的位置出错导致函数无法实现功能。
(4)使用递归函数的优缺点
1>优点:通过有限陈述来定义无限的对象集合的可能性。即使程序没有明确的重复,也可以用有限的递归程序来描述无限次的计算。
2>缺点:当所计算的对象数量多时,内存空间的压力大增。因为每递归调用一次都需开辟新的地址空间。
总结:合理使用自定义函数,不仅可以使代码更加容易调试和阅读,而且可以实现代码复用的功能。例如把一个自定义函数应用到其他案例中。这样可以减少代码重复编写的问题,并大幅提高工作效率。