一. 背景提要
现在老板让你写一个监控程序,监控服务器的系统状况,当cpu\memory\disk等指标的使用量超过阀值时即发邮件报警,你掏空了所有的知识量,写出了以下代码
while True: if cpu利用率 > 90%: #发送邮件提醒 连接邮箱服务器 发送邮件 关闭连接 if 硬盘使用空间 > 90%: #发送邮件提醒 连接邮箱服务器 发送邮件 关闭连接 if 内存占用 > 80%: #发送邮件提醒 连接邮箱服务器 发送邮件 关闭连接
上面的代码实现了功能,但即使是邻居老王也看出了端倪,老王亲切的摸了下你家儿子的脸蛋,说,你这个重复代码太多了,每次报警都要重写一段发邮件的代码,太low了,这样干存在2个问题:
- 代码重复过多,一个劲的copy and paste不符合高端程序员的气质
- 如果日后需要修改发邮件的这段代码,比如加入群发功能,那你就需要在所有用到这段代码的地方都修改一遍
你觉得老王说的对,你也不想写重复代码,但又不知道怎么搞,老王好像看出了你的心思,此时他抱起你儿子,笑着说,其实很简单,只需要把重复的代码提取出来,放在一个公共的地方,起个名字,以后谁想用这段代码,就通过这个名字调用就行了,如下:
def 发送邮件(内容) #发送邮件提醒 连接邮箱服务器 发送邮件 关闭连接 while True: if cpu利用率 > 90%: 发送邮件('CPU报警') if 硬盘使用空间 > 90%: 发送邮件('硬盘报警') if 内存占用 > 80%: 发送邮件('内存报警')
你看着老王写的代码,气势恢宏、磅礴大气,代码里透露着一股内敛的傲气,心想,老王这个人真是不一般,突然对他的背景更感兴趣了,问老王,这些花式玩法你都是怎么知道的? 老王亲了一口你儿子,捋了捋不存在的胡子,淡淡的讲,“老夫,年少时,师从京西沙河淫魔银角大王 ”, 你一听“银角大王”这几个字,不由的娇躯一震,心想,真nb,怪不得代码写的这么6, 这“银角大王”当年在江湖上可是数得着的响当当的名字,只可惜后期纵欲过度,卒于公元2016年, 真是可惜了,只留下其哥哥孤守当年兄弟俩一起打下来的江山。 此时你看着的老王离开的身影,感觉你儿子跟他越来越像了。。。
总结使用函数的好处:
1.代码重用
2.保持一致性,易维护
3.可扩展性
二. 什么是函数?
函数一词来源于数学,但编程中的「函数」概念,与数学中的函数是有很大不同的。
初中数学函数定义:一般的,在一个变化过程中,如果有两个变量x和y,并且对于x的每一个确定的值,y都有唯一确定的值与其对应,那么我们就把x称为自变量,把y称为因变量,y是x的函数。自变量x的取值范围叫做这个函数的定义域
例如 y=2*x
2.1 python函数
函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。
函数能提高应用的模块性,和代码的重复利用率。你已经知道Python提供了许多内建函数,比如print()。但你也可以自己创建函数,这被叫做用户自定义函数。
python中函数定义:函数是逻辑结构化和过程化的一种编程方法。 可以理解为: 函数是指将一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需调用其函数名即可;
特性:
- 减少重复代码
- 使程序变的可扩展
- 使程序变得易维护
2.2 定义一个函数
你可以定义一个由自己想要功能的函数,以下是简单的规则:
- 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号()。
- 任何传入参数和自变量必须放在圆括号中间。圆括号之间可以用于定义参数。
- 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
- 函数内容以冒号起始,并且缩进。
- return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None。
语法定义:
def sayhi(): # def:定义函数的关键字,函数名:sayhi,()内可以指定形参 """The function definitions""" # 文档描述(非必要,但是强烈建议为你的函数添加描述信息) print("Hello, I'm nobody!") # 泛指代码块或程序处理逻辑 sayhi() # 调用函数,通过函数名()的形式
2.3 函数和过程
过程定义:过程就是简单特殊没有返回值的函数
这么看来我们在讨论为何使用函数的的时候引入的函数,都没有返回值,没有返回值就是过程,没错,但是在python中有比较神奇的事情:
def test01(): msg = 'hello The little green frog' print msg def test02(): msg = 'hello WuDaLang' print msg return msg t1 = test01() t2 = test02() print('from test01 return is [%s]' % t1) print('from test02 return is [%s]' % t2)
总结: 当一个函数/过程没有使用return显示的定义返回值时,python解释器会隐式的返回None,
所以在python中即便是过程也可以算作函数。
def test01(): pass def test02(): return 0 def test03(): return 0, 10, 'hello', ['alex', 'lb'], {'WuDaLang': 'lb'} t1 = test01() t2 = test02() t3 = test03() print('from test01 return is [%s]: ' % type(t1), t1) print('from test02 return is [%s]: ' % type(t2), t2) print('from test03 return is [%s]: ' % type(t3), t3)
总结:
返回值数=0:返回None
返回值数=1:返回object
返回值数>1:返回tuple
2.4 函数实例:
实例1:
以下为一个简单的Python函数,它将一个字符串作为传入参数,再打印到标准显示设备上。
def printme( str ): "打印传入的字符串到标准显示设备上" print str return # 调用函数 printme("我要调用用户自定义函数!");
实例2:
传递参数且有返回值的函数
a,b = 5,8 c = a**b print(c) #改成用函数写 def calc(x,y): res = x**y return res #返回函数执行结果 c = calc(a,b) #结果赋值给c变量 print(c)
注: 函数定义阶段只检测函数体的语法,并不会执行
2.5 函数的调用
def foo(): print('from foo') def bar(name): print('bar===>',name)
foo() #调用函数foo
bar() #调用函数bar
按照有参和无参可以将函数调用分两种
foo() # 定义时无参,调用时也无需传入参数 bar('egon') # 定义时有参,调用时也必须有参数
按照函数的调用形式和出现的位置,分三种
foo() #调用函数的语句形式 def my_max(x,y): res=x if x >y else y return res res=my_max(1,2)*10000000 #调用函数的表达式形式 print(res) res=my_max(my_max(10,20),30) #把函数调用当中另外一个函数的参数 print(res)
2.6 函数的返回值
要想获取函数的执行结果,就可以用return语句把结果返回
注意:
- 函数在执行过程中只要遇到return语句,就会停止执行并返回结果,so 也可以理解为 return 语句代表着函数的结束
- 如果未在函数中指定return,那这个函数的返回值为None
- return 一个值 函数调用返回的结果就是这个值
- return 值1,值2,值3,... 返回结果:(值1,值2,值3,...)
2.7 自定义函数
定义函数的三种形式
- 无参数函数:如果函数的功能仅仅只是执行一些操作而已,就定义成无参函数,无参函数通常都有返回值
- 定义有参函数:函数的功能的执行依赖于外部传入的参数,有参函数通常都有返回值
- 空函数
三. 函数参数
形参: 变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只在函数内部有效。函数调用结束返回主调用函数后则不能再使用该形参变量。
实参: 可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。因此应预先用赋值,输入等办法使参数获得确定值。
参数分类
从大的角度去看,函数的参数分两种:形参(变量名),实参(值)
详细的区分函数的参数分为五种:
- 位置参数 #标准调用:实参与形参位置一一对应
- 关键字参数 #关键字调用:位置无需固定
- 默认参数
- 可变长参数(*args,**kwargs)
- 命名关键字参数
位置参数
def foo(x,y,z):#位置形参:必须被传值的参数 print(x,y,z) foo(1,2,3) #位置实参数:与形参一一对应
关键字参数
def foo(x,y,z): print(x,y,z) foo(z=3,x=1,y=2) #正确 实参通过key=value的形式给形参传值 foo(1,z=3,y=2) #正确 位置参数与关键字参数混用 foo(x=1,2,z=3) #错误 关键字实参必须在位置实参后面 foo(1,x=1,y=2,z=3) #错误 不能重复对一个形参数传值
注意:
1. 关键字实参必须在位置实参后面
2. 不能重复对一个形参数传值
默认参数
def register(name,age,sex='male'): #形参:默认参数 print(name,age,sex) register('asb',age=40) register('a1sb',39) register('a2sb',30) register('a3sb',29)
注意:
1. 默认参数必须跟在非默认参数后
def register(sex='male',name,age): #在定义阶段就会报错 print(name,age,sex)
2. 默认参数在定义阶段就已经赋值了,而且只在定义阶段赋值一次
3. 默认参数的值通常定义成不可变类型
非固定参数
若你的函数在定义时不确定用户想传入多少个参数,就可以使用非固定参数
第一种: *args
def foo(x,y,*args): #*会把溢出的按位置定义的实参都接收,以元组的形式赋值给args print(x,y) print(args)
第二种: **kwargs
def stu_register(name,age,*args,**kwargs): # **会把溢出的按关键字定义的实参都接收,以字典的形式赋值给kwargs print(name,age,args,kwargs) stu_register("Alex",22) #输出 #Alex 22 () {}#后面这个{}就是kwargs,只是因为没传值,所以为空 stu_register("Jack",32,"CN","Python",sex="Male",province="ShanDong") #输出 # Jack 32 ('CN', 'Python') {'province': 'ShanDong', 'sex': 'Male'}
命名关键字参数
def foo(name,age,*,sex='male',height): #*后定义的参数为命名关键字参数,这类参数,必须被传值,而且必须以关键字实参的形式去传值 print(name,age) print(sex) print(height) foo('egon',17,height='185')
知识点1:
def foo(name,age=10,*args,sex='male',height,**kwargs): print(name) print(age) print(args) print(sex) print(height) print(kwargs) foo('alex',1,2,3,4,5,sex='female',height='150',a=1,b=2,c=3) # 输出 alex 1 ----->age被位置参数填充 (2, 3, 4, 5) female 150 {'a': 1, 'c': 3, 'b': 2}
知识点2:
def foo(*args): print(args) foo(*(1,2,3,4)) = foo(1,2,3,4) # 传入元组,1,2,3,4 <=====>*(1,2,3,4) 此时,*=* args=(1,2,3,4) # 输出 (1, 2, 3, 4) foo(*['A','B','C','D']) = foo('A','B','C','D') # 传入列表, 此时,['A','B','C','D']相当于('A','B','C','D') # 输出 ('A', 'B', 'C', 'D') foo(['A','B','C','D']) # 传入列表,此时,列表作为输出的元组中的一个元素 # 输出 (['A', 'B', 'C', 'D'],)
知识点3:
def foo(**kwargs): print(kwargs) foo(**{'y': 2, 'x': 1,'a':1}) #foo(a=1,y=2,x=1) #实参传入字典,此时,{'y': 2, 'x': 1,'a':1}相当于a=1,y=2,x=1
知识点4: 接收任意个实参
def wrapper(*args,**kwargs): print(args) print(kwargs) wrapper(1,2,3,a=1,b=2)
知识点5:
def foo(x,y,z): print('from foo',x,y,z) def wrapper(*args,**kwargs): # print(args) #args=(1,) # print(kwargs) #kwargs={'y':3,'z':2} foo(*args,**kwargs) #foo(*(1,),**{'y':3,'z':2}) #foo(1,z=2,y=3)
wrapper(1,z=2,y=3)
# 输出
from foo 1 3 2
四. 局部变量与全局变量
实例:
name = "ShuKe" def change_name(name): print("before change:", name) name = "fengfeng" print("after change", name) change_name(name) print("在外面看看name改了么?", name)
输出
before change: ShuKe after change fengfeng 在外面看看name改了么? ShuKe
总结:
- 在子程序中定义的变量称为局部变量,在程序的一开始定义的变量称为全局变量。
- 全局变量作用域是整个程序,局部变量作用域是定义该变量的子程序。
- 在定义局部变量的子程序内,局部变量起作用;在其它地方全局变量起作用。
五. 前向引用之'函数即变量'
def action(): print 'in the action' logger() action() 报错NameError: global name 'logger' is not defined def logger(): print 'in the logger' def action(): print 'in the action' logger() action() def action(): print 'in the action' logger() def logger(): print 'in the logger' action()
六. 嵌套函数和作用域
看上面的标题的意思是,函数还能套函数?当然可以...
name = "Alex" def change_name(): name = "Alex2" def change_name2(): name = "Alex3" print("第3层打印",name) change_name2() #调用内层函数 print("第2层打印",name) change_name() print("最外层打印",name) #输出 第3层打印 Alex3 第2层打印 Alex2 最外层打印 Alex
作用域在定义函数时就已经固定住了,不会随着调用位置的改变而改变
# 例一: name='alex' def foo(): name='lhf' def bar(): print(name) return bar func=foo() func() # 例二: name='alex' def foo(): name='lhf' def bar(): name='wupeiqi' def tt(): print(name) return tt return bar func=foo() func()
输出
lhf wupeiqi
七. 递归
在函数内部,可以调用其他函数。如果在调用一个函数的过程中直接或间接调用自身本身
def calc(n): print(n) if int(n/2) ==0: return n return calc(int(n/2)) calc(10) 输出: 10 5 2 1
#_*_coding:utf-8_*_ __author__ = 'Linhaifeng' import time person_list=['alex','wupeiqi','yuanhao','linhaifeng'] def ask_way(person_list): print('-'*60) if len(person_list) == 0: return '没人知道' person=person_list.pop(0) if person == 'linhaifeng': return '%s说:我知道,老男孩就在沙河汇德商厦,下地铁就是' %person print('hi 美男[%s],敢问路在何方' %person) print('%s回答道:我不知道,但念你慧眼识猪,你等着,我帮你问问%s...' %(person,person_list)) time.sleep(3) res=ask_way(person_list) # 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("