十一、函数
一、函数的知识体系
什么是函数?
为什么要用函数?
函数的分类:内置函数与自定义函数
如何自定义函数
语法
定义有参数函数,及有参函数的应用场景
定义无参数函数,及无参函数的应用场景
定义空函数,及空函数的应用场景
调用函数
如何调用函数
函数的返回值
函数参数的应用:形参和实参,位置参数,关键字参数,默认参数,*args,**kwargs
高阶函数(函数对象)
函数嵌套
作用域与名称空间
装饰器
迭代器与生成器及协程函数
三元运算,列表解析、生成器表达式
函数的递归调用
内置函数
面向过程编程与函数式编程
二、函数基础
1.什么是函数:
具备某一功能的工具->函数
事先准备工具的过程--->函数的定义
遇到应用场景,拿来就用---->函数的调用
函数分类两大类:
1. 内置函数
2. 自定义函数
2.为何要用函数:
1. 代码冗余
2. 可读性差
3. 可扩展性差
3.如何用函数:
原则:必须先定义,再调用
定义函数的语法:
def 函数名(参数1,参数2,...):
"""文档注释"""
code1
code2
code3
....
return 返回值
调用函数的语法:
函数名(值1,值2,...)什么时候该有返回值? 调用函数,经过一系列的操作,最后要拿到一个明确的结果,则必须要有返回值 通常有参函数需要有返回值,输入参数,经过计算,得到一个最终的结果
什么时候不需要有返回值? 调用函数,仅仅只是执行一系列的操作,最后不需要得到什么结果,则无需有返回值 通常无参函数不需要有返回值
4.调用函数的三种方式:
1 语句形式:foo()
2 表达式形式:3*len('hello')
3 当做另外一个函数的参数:range(len('hello'))
5.函数的参数
函数的参数分为两大类:
形式参数(形参):在定义函数阶段,括号内定义的参数/变量名称为形参
实际参数(实参):在调用函数阶段,括号内传入的值/变量值称为实参
要点:在调用函数阶段会将实参(值)的值绑定给形参(变量名)
细分:
1.位置参数:
1.1位置形参:在定义阶段,按照从左到右的顺序依次定义的形参称之为位置形参
特点:但凡按照位置定义的形参,必须被传值,多一个不行少一个也不行
1.2位置实参:在调用阶段,按照从左到右的顺序依次传入的值称之为位置实参
特点:1.与形参一一对应
2.关键字实参:在调用函数阶段,按照key=value的形式定义的实参称之为关键字实参
特点:可以完全打乱顺序,但依旧能为形参传值(指名道姓的传值)
实参的形式可以是位置实参与关键字实参混合使用,但是必须遵循原则:
1.位置实参必须放在关键字实参的前面
2.不能对同一个形参重复传值
3.默认形参:在定义阶段,就已经为形参赋值,该形参称之为默认参数
特点:
1.定义阶段就已经有值,意味着调用阶段可以不用传值
2.位置形参必须放在默认形参的前面
3.默认形参的值在函数定义阶段就已经固定死了,以后的改动不影响该值
4.默认形参的值通常情况为不可变类型
4.可变长参数:
可变长参数指的是调用阶段,实参值个数是不固定的
*对应溢出的是位置实参
**对应溢出的是关键字实参
4.1 *的用法:
在形参前加*,*会将溢出的位置实参存成元组的形式,然后赋值给*后面的形参变量名
在实参前加*,但凡碰到实参中加*de ,先将实参打散成位置实参,再与形参做对应
4.2**的用法:
在形参前加**,**会将溢出的关键字实参存成字典的形式,然后赋值给**后面的形参变量名
在实参前加**,但凡碰到实参中加**的,先将实参打散成关键字实参的形式,再与形参做对应
5.命名关键字参数:*后定义的参数,必须被传值(有默认值的除外),且必须按照关键字实参的形式传递
可以保证,传入的参数中一定包含某些关键字
def foo(x,y,*args,a=1,b,**kwargs): print(x,y) print(args) print(a) print(b) print(kwargs) foo(1,2,3,4,5,b=3,c=4,d=5) 结果: 2 (3, 4, 5) 3 {'c': 4, 'd': 5}
此乃重点知识!!!
6.函数对象:
函数是第一类对象:指的是函数内存地址可以像一个变量值一样去使用
1.变量值可以被引用
def foo(): #foo=函数的内地址 print('from foo') x=1 #foo=函数的内地址 y=x f=foo print(f) f()
2.变量值可以当作参数传给另一个函数
def foo(): #foo=函数的内地址 print('from foo') def bar(x): print(x) x() x=11111 #foo=函数的内存地址 # bar(x) bar(foo)
3.变量名可以当做容器类型的元素
l=[foo,] print(l) l[0]() dic={'1':foo} print(dic) dic['1']()
7.函数的嵌套:
函数的嵌套调用:在一个函数内部又调用其他函数
函数的嵌套定义:在函数内又定义了其他函数
def func(): def foo(): print('from foo') # print(foo) foo() x=1 print(x) func()
from math import pi def circle(radius,action): def cal_perimeter(): return 2 * pi * radius def cal_area(): return pi * (radius ** 2) if action == 1: res=cal_perimeter() elif action == 2: res=cal_area() return res res=circle(10,1) print(res)
8.函数的名称空间与作用域:
1、名称空间Namespaces:指的就是存放名字与值内存地址绑定关系的地方(内存)
2、名称空间分为三大类:
内置名称空间:存放的是python解释器自带的名字
产生:python解释器的启动则产生
销毁:python解释器的关闭则销毁
全局名称空间:在顶级定义的名字
注意:在fi for while 下的代码也是全局的,不管套几层
产生:执行python程序则产生
销毁:python程序结束则销毁
局部名称空间:在函数内定义的名字
def foo(): m=100 foo()
产生:在函数调用时临时产生
销毁:在函数调用完毕时销毁
三种名称空间产生的先后顺序:内置》全局》局部
查找名字的顺序:从当前位置往外一层一层找
如果当前在局部名称空间:局部》全局》内置
如果当前是在全局名称空间:全局》内置
1.作用域:指的是作用范围
全局作用域:包含内置于全局名称空间的名字
特点:全局存活,全局有效
局部作用域:包含局部名称空间的名字
特点:临时存活,局部有效
!!!!!!!!!重点!!!!!!!!!
作用域关系是在函数定义阶段就已经固定死了,与调用位置无关
# 示范一: def f1(): print(xxx) xxx=111 def f2(): xxx=222 f1() f2() # 示范二: xxx=111 def f1(): print(xxx) # xxx=222 yyy=222 print(yyy) f1()
9.闭包函数:
闭:封闭,指的是该函数是定义一个函数内部的函数
包:该内部函数包含对外层函数名字的引用
为函数体传值的两种方式: def foo(): print('hello %s' %name) 方式一:直接以参数的形式传入 def foo(name): print('hello %s' %name) foo('egon') foo('egon') foo('egon') 方式二:闭包函数 def outter(name): # name='egon' def foo(): print('hello %s' %name) return foo f=outter('egon') # print(f) f() f() f() f1=outter('alex') f1() f1() f1()
10.函数的装饰器:
1.什么是装饰器:
装饰器指的是一个为被装饰对象添加新功能的工具
装饰器本身可以是任意可调用的对象
被装饰的对象也可以是任意可调用的对象
2.为何要用装饰器
开放封闭原则:软件一旦上线就应该对修改封闭,对扩展开放
对修改封闭:
1.不能修改功能的源代码
2.也不能修改功能的调用方式
对扩展开放:
可以为原代码添加新的功能
装饰器就是要在不修改源代码以及调用方式的前提下,为源代码添加新的功能
3.如何使用装饰器
import time def index(): print('welcome to index page') time.sleep(3) def outter(func): # func=最原始那个index的内存地址 def wrapper(): start=time.time() func() # 最原始那个index的内存地址() stop=time.time() print('run time is %s' %(stop-start)) return wrapper index=outter(index) #index=outter(最原始那个index的内地址) #index=wrapper函数的内地址 index() #wraper()
# 无参装饰器的模板 def outter(func): def wrapper(*args,**kwargs): res=func(*args,**kwargs) return res return wrapper
# 有参装饰器的模板 def auth(parameters): def outter(func): def wrapper(*args,**kwargs): print(parameters) res=func(*args,**kwargs) return res return wrapper return outter
from functools import wraps import time def timer(func): @wraps(func) def wrapper(*args,**kwargs): start=time.time() res=func(*args,**kwargs) stop=time.time() print('run is %s'%(stop-start)) return res return wrapper @timer def index(): '''index功能''' print('welcome to index') time.sleep(3) return 123 @timer def home(name): '''home功能''' print('welcome %s to home'%name) time.sleep(2) res=index() print(res) home('egon') print(help(index)) print(index.__name__)
11.叠加多个装饰器:
加载装饰器就是将原函数名偷梁换柱成了装饰器最内层的那个wrapper函数
在加载完毕后,调用函数其实就是在调用wrapper函数
当一个被装饰的对象同时叠加多个装饰器时
装饰器的加载顺序是:自下而上
装饰器内wrapper函数的执行顺序是:自上而下
import time def timmer(func): #func=wrapper2的内存地址 def wrapper1(*args, **kwargs): print('===================================>wrapper1运行了') start=time.time() res = func(*args, **kwargs) #===========================>跳到wrapper2去执行了, stop=time.time() print('run time is %s' %(stop - start)) return res return wrapper1 def auth(engine='file'): def xxx(func): # func=最原始那个index的内存地址 def wrapper2(*args, **kwargs): print('===================================>wrapper2运行了') name=input('username>>>: ').strip() pwd=input('password>>>: ').strip() if engine == 'file': print('基于文件的认证') if name == 'egon' and pwd == '123': print('login successfull') res = func(*args, **kwargs) return res elif engine == 'mysql': print('基于mysql的认证') elif engine == 'ldap': print('基于ldap的认证') else: print('错误的认证源') return wrapper2 return xxx @timmer # index=timmer(wrapper2的内存地址) #index=wrapper1的内存地址 @auth(engine='file') #@xxx #index=xxx(最原始那个index的内存地址) #index=wrapper2的内存地址 def index(): print('welcome to index page') time.sleep(2) index() #wrapper1的内存地址()
12.迭代器:
1.迭代器指的是一个重复的过程,每一次重复都是基于上一次的结果而来的
li=['a','b','c','d','e'] li=('a','b','c','d','e') li='hello' i=0 while i < len(li): print(li[i]) i+=1
迭代器指的是迭代取值的工具,该工具的特点是可以不依赖于索引取值
2.为何要用迭代器
为了找出一种通用的,可以不依赖于索引的迭代取值方式
3.如何使用迭代器
可迭代的对象:但凡内置有.__iter__方法的对象都称之为可迭代对象
迭代器对象:既内置了.__iter__方法,又内置了.__next__方法
关于__iter__方法:
调用可迭代对象的.__iter__()会得到一个迭代器对象
调用迭代器对象的.__iter__()会返回迭代器本身
4.总结迭代器的优缺点:
优点:
1.提供了一种通用的,可以不依赖于索引迭代取值的方式
2.同一时刻在内存中只有一个值,更加节省内存
缺点:
1.取指定值不如索引方便灵活,并且迭代器是一次性取值,不能重复取值
2.无法预知迭代器内数据的个数,只有取完后才能知道
可迭代的对象: str,list,tuple,dict,set,文件对象 迭代器对象: 文件对象 可迭代的对象=====》迭代器对象:调用可迭代对象内置的__iter__方法会有一个返回值,该返回值就是对应的迭代器对象 dic={'x':1,'y':2,'z':3} iter_dic=dic.__iter__() # print(iter_dic) res1=iter_dic.__next__() print(res1) res2=iter_dic.__next__() print(res2) res3=iter_dic.__next__() print(res3) res4=iter_dic.__next__() print(res4) print(dic.__iter__().__next__()) print(dic.__iter__().__next__()) print(dic.__iter__().__next__()) dic={'x':1,'y':2,'z':3} # dic=['a','b','c'] iter_dic=dic.__iter__() # iter_dic=open(r'D:\上海python全栈4期\day13\今日内容',mode='rt',encoding='utf-8') while True: try: print(iter_dic.__next__()) except StopIteration: break
for循环:准确地说应该是迭代器循环,原理如下
1.先调用in后面那个值的__iter__方法,得到迭代器对象
2.执行迭代器.__next()方法,得到一个返回值,然后赋值给一个变量k,运行循环体代码
3.循环往复,直到迭代器取值完毕,抛出异常后,捕捉异常自动结束循环
dic={'x':1,'y':2,'z':3} iter_dic=dic.__iter__() print(iter_dic) print(iter_dic.__iter__()) for k in dic: #iter_dic=dic.__iter__() print(k) with open(r'D:\上海python全栈4期\day13\今日内容',mode='rt',encoding='utf-8') as f: for line in f: #iter_f=f.__iter__() print(line)
12.自定义迭代器:
yield关键字:只能用在函数内
在函数内但凡包含有yield关键字,再去执行函数,就不会立刻运行函数体代码了
会得到一个返回值,该返回值称之为生成器对象,生成器本质就是迭代器
总结yield:
1.提供一种自定义迭代器的解决方案
2.yield可用于返回值
yield VS return
相同点:都可以用于返回值
不同点:yield可以暂停函数,yield可以返回多次值,而return只能返回一次值,函数就立刻结束
def func(): print('=====>第一次') yield 1 print('=====>第二次') yield 2 print('=====>第三次') yield 3 print('=====>第四次') print(func) g=func() print(g.__iter__().__iter__().__iter__() is g) iter(g) #g.__iter__() next(g) #g.__next__() res1=next(g) print(res1) res2=next(g) print(res2) res3=next(g) print(res3) res4=next(g) print(res4)
13.三元表达式,列表生成式,生成器表达式,字典生成式:
三元表达式 def max2(x,y): if x > y: return x else: return y x=10 y=20 res='条件成立的值' if x > y else '条件不成立的值' print(res) 列表生成式 l=[] for i in range(1,11): if i > 4: res='egg%s' %i l.append(res) print(l) l=['egg%s' %i for i in range(1,11) if i > 4] print(l) names=['egon','lxx','yyx','cw','alex','wxx'] l=[] for name in names: if name != 'egon': res='%s_DSB' %name l.append(res) print(l) l=['%s_DSB' %name for name in names if name != 'egon'] print(l) 生成器表达式 res=(i**2 for i in range(3)) print(res) print(next(res)) print(next(res)) print(next(res)) print(next(res)) with open(r'D:\上海python全栈4期\day13\今日内容',mode='rt',encoding='utf-8') as f: data=f.read() print(len(data)) #1025 res=0 for line in f: res+=len(line) print(res) res=sum((len(line) for line in f)) res=sum(len(line) for line in f) print(res) res=max([len(line) for line in f]) res=max((len(line) for line in f)) res=max(len(line) for line in f) print(res) 字典生成式 items=[('name','egon'),('age',18),('sex','male')] dic={} for k,v in items: dic[k]=v print(dic) res={k:v for k,v in items if k != 'sex'} print(res) res={i for i in 'hello'} print(res)
14.匿名函数,函数递归,二分法,面向过程:
1.匿名函数:
匿名函数就是没有名字的函数,特点是只能在定义时使用一次
强调:
1.匿名函数的定义就相当于只产生一个变量值,而没有绑定任何名字
2.所以会在定义完之后就被回收,无法重复使用,只能在定义时使用一次
应用:
1.当某个功能只使用一次就没有再重复使用的必要了,就应该定义成匿名函数
salaries = { 'egon': 3000, 'alex': 100000000, 'wupeiqi': 10000, 'yuanhao': 2000 } print(max(salaries.values()))#比对出来的是value print(max(salaries,key=lambda k:salaries[k])) key=函数的内存地址:作用是控制max函数的比较值 max执行步骤: 1.将可迭代对象变成迭代对象iter 2.next得到一个k,然后将该k当做参数传给key制定的函数,然后调用函数将函数的返回值当做比较依据 3.比较大小,取出最大值对应的k sorted排序 nums=[10,-1,11,9,23] print(sorted(nums)) print(nums) #排序薪资 salaries={ 'egon':3000, 'alex':100000000, 'wupeiqi':10000, 'yuanhao':2000 } print(sorted(salaries,key=lambda k:salaries[k]))#从小到大排列 print(sorted(salaries,key=lambda k:salaries[k],reverse=Ture)) #reverse=Ture从大到小排列 #reverse=False从小到大排列 #每个名字加上sb names = ['alex', 'wupeiqi', 'yuanhao', 'kevin', 'hu老师'] #方式一:手动 new_names=[] for name in names: new_names.append(name+'sb') print(new_names) #方式二:列表生成式 new_names=[name+'sb' for name in names] print(new_names) #方式三:map+匿名函数 res=map(lambda x:x+'sb',names) reduse#合并 方式一:手动实现 res=0 for i in range(101): res+=i print(res) 方式二:列表生成式 print(sum([i for i in range(101)])) 方式三:reduce+匿名函数 from functools import reduce print(reduce(lambda x,y:x+y,[i for i in range(101)],100)) print(reduce(lambda x,y:x+y,[i for i in range(101)])) # filter names=['alex_dsb','wxx_sb','kevin_sb','hu_sb','egon'] # 方式一:手动实现 new_names=[] for name in names: if name.endswith('sb'): new_names.append(name) print(new_names) # 方式二:列表生成式 new_names=[name for name in names if name.endswith('sb')] print(new_names) # 方式三:filter+匿名函数 res=filter(lambda name:name.endswith('sb'),names) print(res) print(list(res))
2.函数递归:
1.函数的递归调用是函数嵌套调用的一种特殊形式
2.特殊在调用一个函数的过程中又直接或者间接地调用了该函数本身
3.递归本质就是一个循环过程;
但是递归必须满足两个原则:
1.每进入一下层递归,问题的规模必须有所减少
2.递归必须有一个明确的结束条件或者说有一个明确进入下一层递归的条件
3.递归有两个明确的阶段:
1.回溯:一层一层地递归调用下去
2.递推:再某一层结束掉递归,然后一层一层返回
4.递归使用:在某些情况下,基于递归来使用重复的过程比while循环更加简单
def f1(): print('from f1') f1() f1() def f2(): print('from f2') f1() def f1(): print('from f1') f2() f1() 递归举例: age(5)=age(4)+2 age(4)=age(3)+2 age(3)=age(2)+2 age(2)=age(1)+2 age(1)=18 age(n)=age(n-1)+2 # n>1 age(1)=18 # n=1 def age(n): if n == 1: return 18 return age(n-1)+2 res=age(5) print(res)
3.二分法:
1.二分法是算法的一种,算法是如何高效地解决问题的思路
nums=[1,13,15,23,27,31,33,57,73,81,93,94,97,101] # 从小到大排列的数字列表 #传统方法: for num in nums: if 58 == num: print('find it') break else: print('not exists') #二分法: def binary_search(): print(nums) if len(nums)==0: print('not exists') mid_index = len(nums) // 2 if find_num > nums[mid_index]: # in the right nums=nums[mid_index+1:] # 重新运行功能,传入新列表 binary_search(find_num,nums) elif find_num < nums[mid_index]: # in the left nums=nums[:mid_index] # 重新运行功能,传入新列表 binary_search(find_num,nums) else: print('find it') binary_search(97,nums)
4.面向过程:
1.核心是过程二字,过程指的是解决问题的步骤,即先干什么后干什么再干什么。。。
2.基于该思想编写程序,脑子里应该始终思考过程二字,就好比在设计一条流水线,是一种机械式的思维方式
优点:复杂的问题流程化,进而简单化
缺点:扩展性差,修改一处代码,可能整个代码都要修改
主用应用:比如程序框架,架构