一 数学定义的函数与python中的函数
初中数学函数定义:一般的,在一个变化过程中,如果有两个变量x和y,并且对于x的每一个确定的值,y都有唯一确定的值与其对应,那么我们就把x称为自变量,把y称为因变量,y是x的函数。自变量x的取值范围叫做这个函数的定义域
例如y=2*x
python中函数定义:函数是逻辑结构化和过程化的一种编程方法。
1 python中函数定义方法: 2 3 def test(x): 4 "The function definitions" 5 x+=1 6 return x 7 8 def:定义函数的关键字 9 test:函数名 10 ():内可定义形参 11 "":文档描述(非必要,但是强烈建议为你的函数添加描述信息) 12 x+=1:泛指代码块或程序处理逻辑 13 return:定义返回值,函数一旦遇到return就立即结束
调用运行:可以带参数也可以不带
函数名()
补充:
1.编程语言中的函数与数学意义的函数是截然不同的俩个概念,编程语言中的函数是通过一个函数名封装好一串用来完成某一特定功能的逻辑,数学定义的函数就是一个等式,等式在传入因变量值x不同会得到一个结果y,这一点与编程语言中类似(也是传入一个参数,得到一个返回值),不同的是数学意义的函数,传入值相同,得到的结果必然相同且没有任何变量的修改(不修改状态),而编程语言中的函数传入的参数相同返回值可不一定相同且可以修改其他的全局变量值(因为一个函数a的执行可能依赖于另外一个函数b的结果,b可能得到不同结果,那即便是你给a传入相同的参数,那么a得到的结果也肯定不同)
2.函数式编程就是:先定义一个数学函数(数学建模),然后按照这个数学模型用编程语言去实现它。至于具体如何实现和这么做的好处,且看后续的函数式编程。
def test(x): #写了参数就要传参数进来 ''' 2*x+1 :param x:整形数字 :return: 返回计算结果 ''' y=2*x+1 return y print(test) #将会打印出函数的内存地址 def test(): #没有定义参数,可以不写 ''' 2*x+1 :param x:整形数字 :return: 返回计算结果 ''' x=3 y=2*x+1 return y a=test() #函数结果需要有一个变量a来接收它 print(a)
# 虽然两个函数一个有参数一个没有参数,但后一个test会将前一个直接覆盖。
# 此时如果输入b = test(3333),会直接报错,新的test不允许传参数
二 为何使用函数
背景提要
现在老板让你写一个监控程序,监控服务器的系统状况,当cpu\memory\disk等指标的使用量超过阀值时,发邮件报警,你掏空了所有的知识量,写出了以下代码
1 while True: 2 if cpu利用率 > 90%: 3 #发送邮件提醒 4 连接邮箱服务器 5 发送邮件 6 关闭连接 7 8 if 硬盘使用空间 > 90%: 9 #发送邮件提醒 10 连接邮箱服务器 11 发送邮件 12 关闭连接 13 14 if 内存占用 > 80%: 15 #发送邮件提醒 16 连接邮箱服务器 17 发送邮件 18 关闭连接
上面的代码实现了功能,但即使是邻居老王也看出了端倪,老王亲切的摸了下你家儿子的脸蛋,说,你这个重复代码太多了,每次报警都要重写一段发邮件的代码,太low了,这样存在2个问题:
- 代码重复过多,一个劲的copy and paste不符合高端程序员的气质
- 如果日后需要修改发邮件的这段代码,比如加入群发功能,那你就需要在所有用到这段代码的地方都修改一遍
你觉得老王说的对,你也不想写重复代码,但又不知道怎么搞,老王好像看出了你的心思,此时他抱起你儿子,笑着说,其实很简单,只需要把重复的代码提取出来,放在一个公共的地方,起个名字,以后谁想用这段代码,就通过这个名字调用就行了,如下
def 发送邮件(内容) #发送邮件提醒 连接邮箱服务器 发送邮件 关闭连接 while True: if cpu利用率 > 90%: 发送邮件('CPU报警') if 硬盘使用空间 > 90%: 发送邮件('硬盘报警') if 内存占用 > 80%: 发送邮件('内存报警')
你看着老王写的代码,气势恢宏、磅礴大气,代码里透露着一股内敛的傲气,心想,老王这个人真是不一般,突然对他的背景更感兴趣了,问老王,这些花式玩法你都是怎么知道的? 老王亲了一口你儿子,捋了捋不存在的胡子,淡淡的讲,“老夫,年少时,师从京西沙河淫魔银角大王 ”, 你一听“银角大王”这几个字,不由的娇躯一震,心想,真nb,怪不得代码写的这么6, 这“银角大王”当年在江湖上可是数得着的响当当的名字,只可惜后期纵欲过度,卒于公元2016年, 真是可惜了,只留下其哥哥孤守当年兄弟俩一起打下来的江山。 此时你看着的老王离开的身影,感觉你儿子跟他越来越像了。。。
总结使用函数的好处:
1.代码重用
2.保持一致性,易维护(群发邮件,一起修改,如添加抄送)
3.可扩展性
三 函数和过程
过程定义:过程就是简单特殊没有返回值的函数
这么看来我们在讨论为何使用函数的的时候引入的函数,都没有返回值,没有返回值就是过程,没错,但是在python中有比较神奇的事情:
def test01(): msg = 'test01' print(msg) def test02(): msg = 'test02' print(msg) return msg def test03(): msg = 'test03' print(msg) return 1,2,3,4,'a',['alex'],{'name':'alex'},None def test04(): msg = 'test03' print(msg) return {'name':'alex'} t1=test01() t2=test02() t3=test03() t4=test04() print(t1) #None print(t2) #test02 返回值只有一个,返回原对象 print(t3) #(1,2,3,4,'a',['alex'],{'name':'alex'},None) 返回值不止一个,结果为所有返回值组成的一个元祖 print(t4) #['name':'alex'] 只有一个字典对象,则类似于test02,返回一个字典对象
总结:当一个函数/过程没有使用return显示的定义返回值时,python解释器会隐式的返回None,
所以在python中即便是过程也可以算作函数。
1 def test01(): 2 pass 3 4 def test02(): 5 return 0 6 7 def test03(): 8 return 0,10,'hello',['alex','lb'],{'WuDaLang':'lb'} 9 10 t1=test01() 11 t2=test02() 12 t3=test03() 13 14 15 print 'from test01 return is [%s]: ' %type(t1),t1 16 print 'from test02 return is [%s]: ' %type(t2),t2 17 print 'from test03 return is [%s]: ' %type(t3),t3
总结:
返回值数=0:返回None
返回值数=1:返回object
返回值数>1:返回tuple
四 函数参数
1.形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只在函数内部有效。函数调用结束返回主调用函数后则不能再使用该形参变量
2.实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。因此应预先用赋值,输入等办法使参数获得确定值
3.位置参数和关键字
位置参数(标准调用):实参与形参位置一一对应;如上例
关键字调用:位置无需固定。c = calc(b = 1, a = 4)
如果上述两种调用方式混合使用,位置参数必须在关键字参数左边(所有传参的大原则)
一个参数绝对不可以传两次值
test(x, y, z) 代码块 test(1,y=2,3) #报错 test(1,3,y=2) #报错: 3先给了y,关键字过来,相当于又将2赋给y,y被传了两次值;同时z没有被赋值 test(1,2,z=3) #报错
test(1,2,z=3,y=4) #报错: 多了一个参数,“有多个值赋给了y”
4.默认参数,在函数参数表中提前就赋了一个值
def handle(x, type='mysql') print(x) print(type) handle('hello') #可以不传值,有默认值 handle('hello','adj') #可以传值,覆盖掉原来的,位置 handle('hello',type='adj') #可以传值,覆盖掉原来的,关键字
5.参数组:**字典,*列
def test(x,*args)
print(x
print(a
test(1,2,3,4,5,6) #1 (2,3,4,5,6)直接当成元祖
#函数后期可以扩展为多个参数值 def test(x,*args) print(x) print(args)#**kwargs接收关键字传值
# 只要是按照位置参数的形式传值,就会集体当做元祖 test(1,2,3,4,5,6) #1 后面集体当成一个元祖(2,3,4,5,6)
test(1,[2,3,4,5,6]) #1 不加*号,表示传入的一个列表也当成整体,作为元祖的一个元素([2,3,4,5,6])
test(1,*[2,3,4,5,6]) #1 加*号,表示传入的一个列表中元素依次遍历,分别放入元祖(2,3,4,5,6)
# *args接收位置传值
def test(x,*args)
print(args) #([2,3,4,5,6])
print(args[0][0]) # 2 索引出了元祖的第一个元素,为列表,再索引出列表的第一个元素
test(1,**{'name':'alex'}) #传字典
##test(1,y=2,z=3) #报错,关键字传法,用*args没有办法接收,它只能接收可以视为一个元祖的
def test(x,**kwargs) #kwargs部分将会输出字典
print(x) #1
print(kwargs)
test(1,y=2,z=3) #{'y':2,'z':3}
def test(x,*args,**kwargs) #只能按这个顺序,因为*args只能接收位置参数传的值,而位置参数只能在关键字参数左边 print(x) #1 print(args) #(1,2,1,1,11,1) print(kwargs) #{'y': 2,'z': 3} test(1,1,2,1,1,11,1,y=2,z=3) ##arg也可以通过索引取出部分值,kwargs可以通过key取 print(arg[-1]) #打印出arg中最后一个元素 print(kwargs['y']) #打印出y作为key的字典value值 def test(x,*args,**kwargs) print(x) #1 print(args,args[-1]) #(1,2,33) 33 print(kwargs, kwargs.get('a')) #{'a': 1} 1 test(1,*[1,2,33],**{'a':1}) ### kwargs['a'] 不能用这种方式取关键字,应该用get(),否则报错
五 局部变量和全局变量
1 name='lhf' 2 3 def change_name(): 4 print('我的名字',name) 5 6 change_name() #调用函数的时候才会执行函数里面的 7 8 # 情况一:无global关键字,声明局部变量 9 def change_name(): 10 name='帅了一笔' # 没有global声明,在函数内部自己创建一个新的局部变量name,无法对全局变量name直接进行赋值 11 print('我的名字',name) # 优先读取局部变量,如果才读取全局变量name 12 13 change_name() 14 print(name) 15 16 17 # 情况二: 有global关键字,无声明局部变量(实际是引用了全局变量),可以直接对全局变量进行所有操作。 18 def change_name(): 19 global name # 有global关键字,将全局的name拿过来,不新创造 20 name='帅了一笔' # 修改上一句声明那拿过来的变量 21 print('我的名字',name) # 我的名字帅了一笔
22
23 change_name()
24 print(name) #帅了一笔
# 情况三:无global关键字,无声明局部变量。可以对全局变量内部元素进行操作:append、remove、clear等都能用 def change_name(): name.append('帅了一笔') # 没有global声明,并没有直接对全局变量name进行赋值,但对可变类型,可以对内部元素进行操作 print('我的名字',name) # 优先读取局部变量,如果才读取全局变量name
#错误示例:有global有声明局部变量 def change_name(): name='帅了一笔' #创建一全局变量 global name #把全局变量叫过来 print('我的名字',name) #直接报错,不知道找哪个了 #因此,每次要使用到全局变量时,一定要把全局变量声明到函数一开始的地方。 #规范:全局变量名都定义为全大写,局部变量名小写
##nonlocal name = "刚娘" def weihou(): name = "陈卓" def weiweihou(): nonlocal name # nonlocal,指定上一级变量,如果没有就继续往上直到找到为止 name = "冷静" weiweihou() print(name) print(name) weihou() print(name) # 刚娘 # 冷静 # 刚娘
##global
name = "刚娘" def weihou(): name = "陈卓" def weiweihou(): global name # globall,指定全局变量(最外层的) name = "冷静" weiweihou() #step2此函数将全局的name改为“冷静” print(name) #step3打印weihou函数中的局部变量“陈卓” print(name) #step1 weihou() print(name) #step4,打印被修改后的全局变量name # 刚娘 # 陈卓 # 冷静
六 前向引用之'函数即变量'
#情况一:内部出现没有定义过的函数,报错 def foo(): print('from foo') bar() foo() #报错 # 修正:定义一下bar()函数 #情况二:在fool函数定义前,定义bar(),可以运行 def bar(): print('from bar') def foo(): print('from foo') bar() foo() #正常运行 # 情况三:在fool函数定义完后再定义bar(),可以运行 def foo(): print('from foo') bar() def bar(): print('from bar') foo() # 情况四:在fool函数执行调用完后再定义bar(),报错 def foo(): print('from foo') bar() foo() def bar(): print('from bar')
七 嵌套函数和作用域
看上面的标题的意思是,函数还能套函数?of course
1 name = "Alex" 2 3 def change_name(): 4 name = "Alex2" 5 6 def change_name2(): 7 name = "Alex3" 8 print("第3层打印",name) 9 10 change_name2() #调用内层函数 11 print("第2层打印",name) 12 13 14 change_name() 15 print("最外层打印",name)
此时,在最外层调用change_name2()会出现什么效果?
没错, 出错了, 为什么呢?
作用域在定义函数时就已经固定住了,不会随着调用位置的改变而改变
1 例一: 2 name='alex' 3 4 def foo(): 5 name='lhf' 6 def bar(): 7 print(name) 8 return bar 9 10 func=foo() 11 func() 12 13 14 例二: 15 name='alex' 16 17 def foo(): 18 name='lhf' 19 def bar(): 20 name='wupeiqi' 21 def tt(): 22 print(name) 23 return tt 24 return bar 25 26 func=foo() 27 func()()
八 递归调用
古之欲明明德于天下者,先治其国;欲治其国者,先齐其家;欲齐其家者,先修其身;欲修其身者,先正其心;欲正其心者,先诚其意;欲诚其意者,先致其知,致知在格物。物格而后知至,知至而后意诚,意诚而后心正,心正而后身修,身修而后家齐,家齐而后国治,国治而后天下平。
在函数内部,可以调用其他函数。如果在调用一个函数的过程中直接或间接调用自身本身
# 递归:直接自己调用自己 def calc(n) print(n) calc(n) #死循环,在内存中以栈的形式保存,会耗尽内存 calc(n) # 递归特性1:一定要有一个结束条件(函数遇到return就终止) def calc(n): print(n) if int(n / 2) == 0: return n res=calc(int(n / 2)) return res res=calc(10) print(res) #运行结果:10 5 2 1
#问路:只有linhaifeng知道
import time person_list=['alex','wupeiqi','yuanhao','linhaifeng','zsc'] def ask_way(person_list): print('-'*60) #判断是否问完了所有人 if len(person_list) == 0: return '根本没人知道' person=person_list.pop(0) #问道linhaifeng,即问到路了 if person == 'linhaifeng': return '%s说:我知道,老男孩就在沙河汇德商厦,下地铁就是' %person print('hi 美男[%s],敢问路在何方' % person) #打印问路语句 print('%s回答道:我不知道,但念你慧眼识猪,你等着,我帮你问问%s...' % (person, person_list)) time.sleep(100) #模拟问路需要时间,一次循环时间延长一段时间 res=ask_way(person_list) #下一个人递归调用本函数。res截取询问结果 print('%s问的结果是: %res' %(person,res)) #应该在此处打印,此处出来了所有结果 return res res=ask_way(person_list) print(res)
运行结果:
递归特性:
1. 必须有一个明确的结束条件
2. 每次进入更深一层递归时,问题规模相比上次递归都应有所减少
3. 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)
堆栈扫盲http://www.cnblogs.com/lln7777/archive/2012/03/14/2396164.html
尾递归优化:http://egon09.blog.51cto.com/9161406/1842475
#二分查找 data = [1, 3, 6, 7, 9, 12, 14, 16, 17, 18, 20, 21, 22, 23, 30, 32, 33, 35] def binary_search(dataset,find_num): print(dataset) if len(dataset) >1: mid = int(len(dataset)/2) if dataset[mid] == find_num: #find it print("找到数字",dataset[mid]) elif dataset[mid] > find_num :# 找的数在mid左面 print("