Python成长之路【第四篇】:Python基础之函数
一、数学定义的函数与python中的函数
初中数学函数定义:一般的,在一个变化过程中,如果有两个变量x和y,并且对于x的每一个确定的值,y都有唯一确定的值与其对应,那么我们就把x称为自变量,把y称为因变量,y是x的函数。自变量x的取值范围叫做这个函数的定义域
例如y=2*x
python中函数定义:函数是逻辑结构化和过程化的一种编程方法:
python中函数定义方法:
def test(x):
"The function definitions"
x+=1
return x
def:定义函数的关键字
test:函数名
():内可定义形参
"":文档描述(非必要,但是强烈建议为你的函数添加描述信息)
x+=1:泛指代码块或程序处理逻辑
return:定义返回值
调用运行:可以带参数也可以不带
函数名()
补充:
1、编程语言中的函数与数学意义的函数是截然不同的俩个概念,编程语言中的函数是通过一个函数名封装好一串用来完成某一特定功能的逻辑,数学定义的函数就是一个等式,等式在传入因变量值x不同会得到一个结果y,这一点与编程语言中类似(也是传入一个参数,得到一个返回值),不同的是数学意义的函数,传入值相同,得到的结果必然相同且没有任何变量的修改(不修改状态),而编程语言中的函数传入的参数相同返回值可不一定相同且可以修改其他的全局变量值(因为一个函数a的执行可能依赖于另外一个函数b的结果,b可能得到不同结果,那即便是你给a传入相同的参数,那么a得到的结果也肯定不同)
2、函数式编程就是:先定义一个数学函数(数学建模),然后按照这个数学模型用编程语言去实现它。至于具体如何实现和这么做的好处,且看后续的函数式编程。
二、为何使用函数
现在老板让你写一个监控程序,监控服务器的系统状况,当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%:
发送邮件('内存报警')
总结使用函数的好处:
1.代码重用
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
四、函数参数
1、形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只在函数内部有效。函数调用结束返回主调用函数后则不能再使用该形参变量
2、实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。因此应预先用赋值,输入等办法使参数获得确定值
3、位置参数和关键字(标准调用:实参与形参位置一一对应;关键字调用:位置无需固定)
4、如果传参时,位置参数和关键字参数同时存在,位置参数必须在关键字参数左边
4、默认参数
5、参数组

1 # 例: 2 # 默认参数 3 # def handle(x, type = "mysql"): 4 # print(x) 5 # print(type) 6 # handle("hello") 7 # 输出: 8 # hello 9 # mysql 10 # ps:默认参数就是不用传入实参也会使用 11 12 # 例: 13 # 列表参数 14 # def test(x, *args): 15 # print(x) 16 # print(args) 17 # print(args[0]) 18 # test(1, 2, 3, 4, 5, 6) 19 # 输出: 20 # 1 21 # (2, 3, 4, 5, 6) 22 # 2 23 24 # 例: 25 # 字典参数 26 # def test(x,**kwargs): 27 # print(x) 28 # print(kwargs) 29 # test(1, y=2, z=3) 30 # 输出: 31 # 1 32 # {'y': 2, 'z': 3}
五、函数的定义和使用
def 函数名(参数):
...
函数体
...
返回值
函数的定义主要有如下要点:
- def:表示函数的关键字
- 函数名:函数的名称,日后根据函数名调用函数
- 函数体:函数中进行一系列的逻辑计算,如:发送邮件、计算出 [11,22,38,888,2]中的最大数等...
- 参数:为函数体提供数据
- 返回值:当函数执行完毕后,可以给调用者返回数据。
1、返回值
函数是一个功能块,该功能到底执行成功与否,需要通过返回值来告知调用者。
以上要点中,比较重要有参数和返回值:
def 发送短信():
发送短信的代码...
if 发送成功:
return True
else:
return False
while True:
# 每次执行发送短信函数,都会将返回值自动赋值给result
# 之后,可以根据result来写日志,或重发等操作
result = 发送短信()
if result == False:
记录日志,短信发送失败...
2、参数
为什么要有参数?
1 def CPU报警邮件() 2 #发送邮件提醒 3 连接邮箱服务器 4 发送邮件 5 关闭连接 6 7 def 硬盘报警邮件() 8 #发送邮件提醒 9 连接邮箱服务器 10 发送邮件 11 关闭连接 12 13 def 内存报警邮件() 14 #发送邮件提醒 15 连接邮箱服务器 16 发送邮件 17 关闭连接 18 19 while True: 20 21 if cpu利用率 > 90%: 22 CPU报警邮件() 23 24 if 硬盘使用空间 > 90%: 25 硬盘报警邮件() 26 27 if 内存占用 > 80%: 28 内存报警邮件()
1 def 发送邮件(邮件内容) 2 3 #发送邮件提醒 4 连接邮箱服务器 5 发送邮件 6 关闭连接 7 8 9 while True: 10 11 if cpu利用率 > 90%: 12 发送邮件("CPU报警了。") 13 14 if 硬盘使用空间 > 90%: 15 发送邮件("硬盘报警了。") 16 17 if 内存占用 > 80%: 18 发送邮件("内存报警了。")
函数的有三中不同的参数:
- 普通参数
- 默认参数
- 动态参数
1 # ######### 定义函数 ######### 2 3 # name 叫做函数func的形式参数,简称:形参 4 def func(name): 5 print(name) 6 7 # ######### 执行函数 ######### 8 # 'albert' 叫做函数func的实际参数,简称:实参 9 func('albert')
1 def func(*args): 2 3 print(args) 4 5 # 执行方式一 6 func(11,33,4,4454,5) 7 8 # 执行方式二 9 li = [11,2,2,3,3,4,54] 10 func(*li)
1 def func(**kwargs): 2 3 print(kwargs) 4 5 # 执行方式一 6 func(name="albert",age=18) 7 8 # 执行方式二 9 li = {"name":"albert", "age":18, "gender":"male"} 10 func(**li)
1 def func(*args, **kwargs): 2 3 print(args) 4 print(kwargs) 5 6 func(1, 2, 3, **{"name":2})
六、局部变量和全局变量
在子程序中定义的变量称为局部变量,在程序的一开始定义的变量称为全局变量。
# name = 'Albert' # 全局变量
# def change_name():
# print('我的名字', name)
# change_name()
#
#
# def change_name():
# name = '帅的一比' # 局部变量
# print('我的名字', name) # 局部变量
# change_name()
# print(name) # 全局变量
# def change_name():
# global name # 引用全局变量
# name = '帅的一比' # 修改全局变量
# print('我的名字', name) # 修改后的全局变量
# change_name()
# print(name) # 修改后的全局变量
一、如果函数的内容无global关键字
# - 有声明局部变量
# NAME = ["小明","小红"]
# def foo():
# NAME = "小刚"
# print('他是', NAME)
# foo()
# - 无声明局部变量
# NAME = ["小明","小红"]
# def foo():
# NAME.append("小李")
# print('输出:', NAME)
# foo()
二、如果函数的内容有global关键字
# - 有声明局部变量
# NAME = ["小明","小红"]
# def foo():
# global NAME
# NAME = "小刚"
# print('他是', NAME)
# foo()
# 错误示例
# NAME = ["小明","小红"]
# def foo():
# NAME = "小刚"
# global NAME
# print('他是', NAME)
# foo()
# 报错,global 应该在局部变量之前
# - 无声明局部变量
# NAME = ["小明","小红"]
# def foo():
# global NAME
# NAME = ["小刚"]
# NAME.append('小李')
# print('他是', NAME)
# foo()
优先读取局部变量,如果没有局部变量,将会读取全局变量,无法对全局变量重新赋值
如果函数中有global关键字,global后的变量本质上就是全局的那个变量,可读取可赋值
1 # NAME = "小红" 2 # 3 # def foo(): 4 # NAME = "小明" 5 # print("我是:", NAME) 6 # 7 # foo() 8 # ps:如果函数体中,没有global关键字,优先读取局部变量,局部找不到,才会去读取全局变量,无法对全局变量赋值
1 # NAME = "小红" # 全局变量 2 # 3 # def foo(): 4 # global NAME # 引用全局变量 5 # print("我是:", NAME) # 打印全局变量 6 # NAME = "小李" # 修改全局变量 7 # print("我是:", NAME) # 打印修改后的全局变量 8 # 9 # foo() # 执行函数 10 # ps:如果函数中有global关键字,变量本质上就是全局的那个变量,可读取可赋值
1 # name = "小红" 2 3 # def foo(): 4 # name = "小明" 5 # def nal(): 6 # nonlocal name 7 # name = "小刚" 8 # nal() 9 # print(name) 10 # print(name) 11 # foo() 12 # print(name) 13 14 # 输出: 15 # 小红 16 # 小刚 17 # 小红 18 # ps:nonlocal,指定上一级变量
1 NAME = '小明' # 1 2 3 def foo(): # 2 4 name = "小红" # 4 5 print(name) # 5 6 def awe(): # 6 7 name = "小刚" # 8 8 print(name) # 9 9 def nal(): # 10 10 name = '王元鹅' # 13 11 print(name) # 14 12 print(name) # 11 13 nal() # 12 14 awe() # 7 15 print(name) # 15 16 17 foo() # 3
七、前向引用之“函数即变量”
# def action():
# print('in the action')
# logger()
# action()
# 报错NameError: 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()
八、嵌套函数和作用域
看上面的标题的意思是,函数还能套函数?of course
name = "Albert"
def change_name():
name = "Albert2"
def change_name2():
name = "Albert3"
print("第3层打印", name)
change_name2() # 调用内层函数
print("第2层打印", name)
change_name()
print("最外层打印", name)
此时,在最外层调用change_name2()会出现什么效果?
没错, 出错了, 为什么呢?
作用域在定义函数时就已经固定住了,不会随着调用位置的改变而改变
# 例一: # name='albert' # # def foo(): # name='seven' # def bar(): # print(name) # return bar # # func=foo() # func() # 例二: # name='albert' # # def foo(): # name='seven' # def bar(): # name='avlin' # def tt(): # print(name) # return tt # return bar # # func=foo() # func()()
九、递归调用
古之欲明明德于天下者,先治其国;欲治其国者,先齐其家;欲齐其家者,先修其身;欲修其身者,先正其心;欲正其心者,先诚其意;欲诚其意者,先致其知,致知在格物。物格而后知至,知至而后意诚,意诚而后心正,心正而后身修,身修而后家齐,家齐而后国治,国治而后天下平。
在函数内部,可以调用其他函数。如果在调用一个函数的过程中直接或间接调用自身本身
# def calc(n): # print(n) # if int(n / 2) == 0: # return n # return calc(int(n / 2)) # # calc(10) # 输出: # 10 # 5 # 2 # 1
1 # import time 2 # 3 # person_list=['alex','avlin','seven','albert'] 4 # def ask_way(person_list): 5 # print('-'*60) 6 # if len(person_list) == 0: 7 # return '没人知道' 8 # person=person_list.pop(0) 9 # if person == 'albert': 10 # return '%s说:我知道,西天就在西边,念个咒语就行' %person 11 # print('hi 美男[%s],敢问路在何方' %person) 12 # print('%s回答道:我不知道,但念你慧眼识猪,你等着,我帮你问问%s...' %(person,person_list)) 13 # time.sleep(3) 14 # res=ask_way(person_list) 15 # # print('%s问的结果是: %res' %(person,res)) 16 # return res 17 # 18 # res=ask_way(person_list) 19 # 20 # print(res) 21 22 # 递归问路
递归特性:
1. 必须有一个明确的结束条件
2. 每次进入更深一层递归时,问题规模相比上次递归都应有所减少
3. 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)
堆栈扫盲http://www.cnblogs.com/lln7777/archive/2012/03/14/2396164.html
1 data = [1, 3, 6, 7, 9, 12, 14, 16, 17, 18, 20, 21, 22, 23, 30, 32, 33, 35] 2 # 3 # 4 # def binary_search(dataset, find_num): 5 # print(dataset) 6 # 7 # if len(dataset) > 1: 8 # mid = int(len(dataset) / 2) 9 # if dataset[mid] == find_num: # find it 10 # print("找到数字", dataset[mid]) 11 # elif dataset[mid] > find_num: # 找的数在mid左面 12 # print("\033[31;1m找的数在mid[%s]左面\033[0m" % dataset[mid]) 13 # return binary_search(dataset[0:mid], find_num) 14 # else: # 找的数在mid右面 15 # print("\033[32;1m找的数在mid[%s]右面\033[0m" % dataset[mid]) 16 # return binary_search(dataset[mid + 1:], find_num) 17 # else: 18 # if dataset[0] == find_num: # find it 19 # print("找到数字啦", dataset[0]) 20 # else: 21 # print("没的分了,要找的数字[%s]不在列表里" % find_num) 22 # 23 # 24 # binary_search(data, 23)
十、lambda表达式
学习条件运算时,对于简单的 if else 语句,可以使用三元运算来表示,即:
# 普通条件语句
if 1 == 1:
name = 'Albert'
else:
name = 'seven'
# 三元运算
name = 'Albert' if 1 == 1 else 'seven'
对于简单的函数,也存在一种简便的表示方式,即:lambda表达式
# ###################### 普通函数 ######################
# 定义函数(普通方式)
def func(arg):
return arg + 1
# 执行函数
result = func(123)
# ###################### lambda ######################
# 定义函数(lambda表达式)
my_lambda = lambda arg: arg + 1
# 执行函数
result = my_lambda(123)
十一、函数式编程
面向过程解释:
函数的参数传入,是函数吃进去的食物,而函数return的返回值,是函数拉出来的结果,面向过程的思路就是,把程序的执行当做一串首尾相连的函数,一个函数吃,拉出的东西给另外一个函数吃,另外一个函数吃了再继续拉给下一个函数吃。。。
例如:
用户登录流程:前端接收处理用户请求-》将用户信息传给逻辑层,逻辑词处理用户信息-》将用户信息写入数据库
验证用户登录流程:数据库查询/处理用户信息-》交给逻辑层,逻辑层处理用户信息-》用户信息交给前端,前端显示用户信息
函数式编程:
高阶函数:
满足俩个特性任意一个即为高阶函数
1、函数的传入参数是一个函数名
2、函数的返回值是一个函数名
1 array=[1,3,4,71,2] 2 3 ret=[] 4 for i in array: 5 ret.append(i**2) 6 print(ret) 7 8 #如果我们有一万个列表,那么你只能把上面的逻辑定义成函数 9 def map_test(array): 10 ret=[] 11 for i in array: 12 ret.append(i**2) 13 return ret 14 15 print(map_test(array)) 16 17 #如果我们的需求变了,不是把列表中每个元素都平方,还有加1,减一,那么可以这样 18 def add_num(x): 19 return x+1 20 def map_test(func,array): 21 ret=[] 22 for i in array: 23 ret.append(func(i)) 24 return ret 25 26 print(map_test(add_num,array)) 27 #可以使用匿名函数 28 print(map_test(lambda x:x-1,array)) 29 30 31 #上面就是map函数的功能,map得到的结果是可迭代对象 32 print(map(lambda x:x-1,range(5))) 33 print(list(map(lambda x:x-1,range(5))))
1 # 找出姓 WE的人名 2 people=['Albert','Seven','Title','WE_Titile','WE_Seven','WE_Miss'] 3 4 def tell_WE(x): 5 return x.startswith('WE') 6 7 8 def filter_test(func,array): 9 ret=[] 10 for i in array: 11 if func(i): 12 ret.append(i) 13 return ret 14 15 print(filter_test(tell_WE,people)) 16 17 18 #函数filter,返回可迭代对象 19 print(filter(lambda x:x.startswith('WE'),people)) 20 print(list(filter(lambda x:x.startswith('WE'),people)))
1 from functools import reduce 2 #合并,得一个合并的结果 3 array_test=[1,2,3,4,5,6,7] 4 array=range(100) 5 6 #报错啊,res没有指定初始值 7 def reduce_test(func,array): 8 l=list(array) 9 for i in l: 10 res=func(res,i) 11 return res 12 13 # print(reduce_test(lambda x,y:x+y,array)) 14 15 #可以从列表左边弹出第一个值 16 def reduce_test(func,array): 17 l=list(array) 18 res=l.pop(0) 19 for i in l: 20 res=func(res,i) 21 return res 22 23 print(reduce_test(lambda x,y:x+y,array)) 24 25 #我们应该支持用户自己传入初始值 26 def reduce_test(func,array,init=None): 27 l=list(array) 28 if init is None: 29 res=l.pop(0) 30 else: 31 res=init 32 for i in l: 33 res=func(res,i) 34 return res 35 36 print(reduce_test(lambda x,y:x+y,array)) 37 print(reduce_test(lambda x,y:x+y,array,50))
1 #当然了,map,filter,reduce,可以处理所有数据类型 2 3 name_dic=[ 4 {'name':'小明','age':1000}, 5 {'name':'小红','age':10000}, 6 {'name':'小刚','age':9000}, 7 {'name':'小强','age':18}, 8 ] 9 #利用filter过滤非正常年龄 10 def func(x): 11 age_list=[1000,10000,9000] 12 return x['age'] not in age_list 13 14 15 res=filter(func,name_dic) 16 for i in res: 17 print(i) 18 19 res=filter(lambda x:x['age'] == 18,name_dic) 20 for i in res: 21 print(i) 22 23 24 #reduce用来计算1到100的和 25 from functools import reduce 26 print(reduce(lambda x,y:x+y,range(100),100)) 27 print(reduce(lambda x,y:x+y,range(1,101))) 28 29 #用map来处理字符串列表啊,把列表中所有人都变成nb,比方Albert_nb 30 name=['Albert','Seven','nalio'] 31 32 res=map(lambda x:x+'_nb',name) 33 for i in res: 34 print(i)
十二、zip 函数
zip 被称为拉链,一一对应,按照最短的长度匹配
zip 函数的基本用法
# 每一个元素一一对应,并当作为一个元组
# z = zip(("a", "b", "c"),(1, 2, 3))
# l = list(z)
# print(l)
# 如果 zip 两个参数中的元素数量不一致的时候,会按照最少的进行匹配
# z = zip(("a", "b", "c", "d"),(1, 2, 3))
# l = list(z)
# print(l)
字符串也可以用 zip 函数,因为同样是序列、可迭代对象
z = zip("hello", "12345")
l = list(z)
print(l)
如果我们有一个字典,想要将他的 keys 和 values 匹配到一个元组中,就可以利用 zip 这个函数
p = {"name":"albert", "age":18, "gender":"male"}
z = zip(p.keys(), p.values())
l = list(z)
print(l)
十三、max min 函数
1、max 函数处理的是可迭代对象,相当于一个 for 循环取出每一个元素进行比较,注意:不同类型之间不能进行比较
2、每个元素间进行比较,是从每个元素的第一个位置依次比较,如果这一个位置分出大小,后面的都不需要比较了,直接得出这俩元素的大小
1 # max 高阶用法 2 3 # 这里有个列表,我们用max方法找出他的最大值 4 # l = [1, 3,100, -1, 2] 5 # print(max(l)) 6 # --> 100 7 8 # 但是如果我们找最大值得对象是个字典呢? 9 # dic = {"age1":18, "age2":20} 10 # print(max(dic)) 11 # --> age2 12 # 打印出来的是keys,并没有达到我们想要的效果 13 14 # 当然有的同学很聪明,他用了下面这个方法 15 # dic = {"age1":18, "age2":20} 16 # print(max(dic.values())) 17 # --> 20 18 # 但是这个打印出来的只是values的值,我们并拿不到对应的key 19 20 # 于是,我们可以结合 zip 这个函数来操作 21 # dic = {"age1":18, "age2":20} 22 # print(max(zip(dic.values(), dic.keys()))) 23 # --> (20, 'age2') 24 # 这样我们就既拿到了 values 也拿到了 keys 25 26 27 28 # 终极应用 29 30 # people = [ 31 # {"name":"小明", "age":18}, 32 # {"name":"小红", "age":15}, 33 # {"name":"小刚", "age":19}, 34 # {"name":"小强", "age":32} 35 # ] 36 # 37 # print(max(people, key=lambda x:x["age"])) 38 # --> {'name': '小强', 'age': 32} 39 40 # 这样就达到了我们想要的效果 41 42 # min 就是比较最小值的,和 max 用法一样的
十四、内置函数

1 # ord() 是chr()的对应函数 ,返回字符对应的整数值。 2 3 # print(chr(65)) 4 # 输出对应ascll码对应的字符 5 # --> A 6 7 # print(ord('a')) 8 # 输出对应ascll码对应的整数值 9 # --> 97

浙公网安备 33010602011771号