python函数
一、python 函数
1.函数
1.1函数定义
python 函数是由若干个语句块,函数名称,参数列表构成,他是组织代码最小的单元.
具有一定的完成功能.**
2.2函数的作用
结构化编程对代码进行最基本的封装,按照功能组织代码
封装是为了复用,较少冗余代码
代码更加简洁,更容易读懂
3.3函数分类
内建函数 如max() min() reversed()
函数库如math.ceil()
自定义函数 使用def() 定义
2.函数定义
def 函数名 (参数列表)
函数体(代码块)
[return 返回值]
函数名即为函数标识符,命名要求一样
语句块必须缩进,python要求语句缩进严格
函数如果没有return那么会默认返回none空
定义中的参数列表是一种形式参数,即为形参
3.函数调用
函数定义即为声明了一个函数还并未调用,他不能被执行只能被调用
调用方式 即在函数后方加入() ,如有必要需要在()中加入参数
调用时写入的参数为实际参数,即为实参
def add(x,y): #函数定义
result = x + y #代码块
return result #返回值
out = add(5,6) #调用函数
print (out) #打印输出值
#函数在调用时采用占用内存
解析:
定义了一个函数名为add的函数,其形参为x,y
该函数为计算 x + y 结果并且赋值给result,提供返回值
调用时需要提供两个实参赋值给x和y, 此案例实参为 5和 6
函数名也是标识符
返回值也是值,默认不提供return那么会返回none
定义时必须是已存在函数否则爆NameError
函数是可调用对象,所以可以被callable(add) 回车 true
4.函数参数
函数定义即为定义形参,调用时调用实参,一般来说形参和实参对应(可变形参除外)
4.1 实参传参
1.位置传参
定义时(x,y,z),调用时(1,2,3)
2.关键字传参
定义时(x,y,z)调用时(x=1,z=2,y=3) 使用形参名字传入实参,那么就无所谓位置
def add(x,y):
print (x)
print (y)
print ('_' * 30)
add(4,5)
add(5,4)
add(x=4,y=5)
add(x=[4],y=(5,))
add(x=4.1,y=5.1)
add(x=5,4)
add(y=4,5)
#测试以上实验都是什么结果 为什么?
切记: 传参是指调用时穿的实参,就两种方式,一个是位置一个是关键字不可混用
4.2形参缺省值
给予形参一个缺省值,
def add(x=1,y=2):
print (x)
print (y)
print ('_' * 10 )
add(3)
add(2,3)
add(x=3,y=4)
#各输出什么?
# 定义一个函数login,参数名称为host、port、username、password
def mysqllogin(host='mysql_login.com',port='3306',username='admin',password='123456'):
print ('mysql://{2}:{1}@{0}:{3}'.format(host,port,username,password))
mysqllogin()
mysqllogin('127.0.0.1')
mysqllogin('127.0.0.1','3307','mysql_user','mysql_password')
#会输出什么?
当给予形参默认值时,当调用函数传入实参时只给部分实参,那么就会使用形参缺省值
4.3可变参数
*args 打包输出元组, *kwargs是打包输出字典
需求: 需要写一个函数对多个数字进行求和
def sum(k):
s = 0
for i in k :
s += i
return s
print (sum([1,2,3,4,5,6]) ) #直接替代为range(10) 可以吗?
#此方法不支持元组,因为+=不支持int和list
def sum(*nums) :
s = 0
for i in nums:
s +=i
return s
pirnt (sum(1,2,3,4,5,6))
传入可迭代参数,并且累加每一个参数
*代表可变位置参数,接受多个实参,他将收集的实参组织到一个元组中tuple\
4.3.1可变位置参数
即为*,比如以上案例使用 *nums,代表可变位置参数,接受多个实参放入tuple
4.3.2可变关键字参数
**代表可变关键字参数,接受多个关键字传参
将收集的值组织到一个字典中
def showconfig(**kwargs):
for k,v in **kwargs :
print ('{}={}'.format(k,v),end=',')
showconfig(host='127.0.0.1',port='3306',username='admin',password='123456')
def showconfig(username,password,**kwargs,*args):
for k,v in kwargs.items():
print('{}={}'.format(k,v), end=', ')
showconfig(host='127.0.0.1', port=8080, username='wayne', password='magedu')
def showconfig(username,password,*args,**kwargs):
for k,v in kwargs.items():
print('{}={}'.format(k,v), end=', ')
showconfig(host='127.0.0.1', port=8080, username='wayne', password='magedu')
def showconfig(username,password,**kwargs):
for k,v in kwargs.items():
print('{}={}'.format(k,v), end=', ')
print (**kwargs,username,password)
showconfig(host='127.0.0.1', port=8080, username='wayne', password='magedu')
#想想输出结果,为什么?
总结:
可变位置参数*表示, 可变关键字传参 **表示
可变位置传参将参数收集成tuple元组 可变关键字传参收集成dict 字典
在混合使用时普通形参要放到前边,可变位置参数要放到可变关键字传参前边
练习:
def comprehensive(*args,**kwargs):
for i in args:
print (i)
for k,v in kwargs.items():
print (k,v )
l1=[*range(10)]
comprehensive(l1,host='127.0.0.1',username='123')
#输出结果?
def fn(x,y,*args,**kwargs):
print (x,y,args,kwargs,end = '\n')
fn(1,2,3,4,5,6,7,x=1,y=2,a='123',b='abc')
fn(x=1,y=2,1,2,3,4,a='abc',b='123')
#位置参数跟随关键参数!这是不被允许的!
SyntaxError: positional argument follows keyword argument
4.4 keyword-only参数
def fn(*args, x, y, **kwargs):
print(args, x, y, kwargs, end='\n')
fn(1,2,3)
fn(1,2,3,a='123',b=2)
fn(1, 2, 3, 4, x=1, y=2, a='123', b=3)
#TypeError: fn() missing 2 required keyword-only arguments: 'x' and 'y'
当python3 更新之后定义一个keyword-only函数,在一个*可变位置参数之后的形参都会被定义为keyword-only,必须传入.
变相思考,是不是*args可变位置参数已经获取了全部参数位置带入到tuple中,不可使用位置参数只能使用关键字传参.
那么**kwargs可以吗,可变关键字传参?
def fn(**kwagrs,x,y):
print (kwargs,x,y)
fn(host='127.0.0.1',username='123',y='100')
#SyntaxError: invalid syntax
可变关键字传参概念:将输入的参数存储到dict,那么x,y是不是关键字?就算写入x=也无法得到值
语法不存在
如果想要所有形参都变成keyword-only
def fn(*,x,y) :
print (x,y)
fn(x=10)
#TypeError: fn() missing 1 required keyword-only argument: 'y'
总结在*可变位置传参后所以形参都会变成必须需要传入的关键字参数.
4.5 Positional-only参数
def fn( a , / ) :
print (1)
fn(a=10)
fn(10)
#TypeError: fn() got some positional-only arguments passed as keyword arguments: 'a'
翻译过来的意思仅能使用位置传参,所以说比较鸡肋不太使用.
4.6 参数混合使用
将 *可变位置参数 **可变关键字参数 x='abc’缺省值 *,a=1 keyword-one ,/Positional-only进行配合使用
def fn( *args,a=10, ): #可变位置传参,关键字传参
print( args, a)
def fn2(x,*args,y=100) : #位置传参,可变位置传参,关键字传参
print (x,args,y)
def fn3(x,y,*args,k=100,**kwargs) : #位置传参,可变位置传参,关键字传参,可变关键字传参
print (x,y,args,k,kwargs)
def fn4(*args,x,y,k='abc',**kwargs): #可变位置传参,keyword-noly,关键字传参,可变关键字传传参
print (args,x,y,k,kwargs)
def fn5 (x=100,y=200,k,*args,**kwargs, / ):
print(x,y,k,args,kwargs)
fn(1)
fn(1,20,a=30)
fn2(1,1,2,3,3,4,5,6,y=30)
fn3(1,2,2,3,4,5,6,l=100,n=200,k=500)
fn4(1,2,4,4,5,6,x=100,y=200,k=600,mm='sun',gg='wangwang') #爆x,y,keyword-only关键字传参
fn5(x=200,y=300,100,1,2,3,4,56,kw=10000,wk=20000) #fn / Positional-only只支持前边是关键值传参
4.7 参数使用规则
一般来说写函数使用参数一定要简单易懂
一般的使用规则为: Positional-only,普通参数,缺省参数,可变位置参数,keyword-only(带缺省值),可变关键字参数.
def fn(a,b,/,c,d,e=200,*args,k=100,w=299,**kwargs):
print (a,b,c,d,e,args,k,w,kwargs)
fn(100,200.300,400.500,1,2,3,4,5,k=200,w=300,admin='liu')
#应用
def com(host='127.0.0.1',username='admin',password='123456',port='3306',**kwargs):
print ('mysql://{}:{}@{}:{}\{}'.format(username,password,host,port,kwargs.get('db','test')))
com(db='wordpress')
com(host="192.168.1.100",username='Administrator',password='Liujinxin@123',port='33060',db='work-01')
总结: 定义最常使用参数为普通参数,不是提供缺省值,需要客户提供,注意参数顺序.
将必须使用的参数定义为keyword-only参数,必须携带的,要求关键字必须传入
如果函数有很多的参数无法逐一定义,可以使用可变关键字传参.
5. 参数解构
def add(x,y):
print (x,y)
return x + y
sum=4,5
add(4,5)
# add((4,5)) 有问题 需要两个参数,但是这里只有一个
add(sum[0],sum[1])
add(*sum)
add(*(4,5))
add(*{4,5})
add(*[4,5])
add(*range(4,6))
add( *{'a' : '10' , 'b' : '20'} )
#add( **{'a' : '10' , 'b' : '20'} ) 有问题 会得到意外参数a,机构后 a=10,b=20
add( **{'x' : '10' , 'y' : '20'} )
总结
解构时*会理解为位置传参,而**会理解为关键字传参随后匹配函数的实参
提取出来的元素回合函数的形参对比
二、函数返回值
returnn
实验返回值返回个数,返回后函数还会继续执行吗,多个返回值是否可以
def add(x,y):
k = x + y
return k
print ("123")
def fn1(x):
print (x)
return x + 1
return x + 1
def fn2(y):
if y = 2 :
return y + 1
elif y < 3 :
return y + 2
add(4,5)
fn1(10)
fn2(2)
#实验一下
总结:
函数都有返回值,函数使用return作为返回值
函数返回值不写,隐式调用return none
return 不一定是函数的最后一句
一个函数可以有多个return 但是只会执行一个,如果没有执行那么是隐式none
当执行return后,后续函数不再执行,
return用来结束函数调用,返回返回值
如果有必要可调用隐式return
三、函数作用域
1. 作用域
标识符可见范围作用域函数中
def foo():
x = 100
print (x)
#会不会访问到? NameError: name 'x' is not defined
不会被访问到,甚至会报错NameError
因为x只定义在foo函数中,而没用定义在全局
2.作用域分类
1.全局作用域
1.整个程序运行环境可见
2.全局作用域变量称之为全局变量global
2.局部作用域
1.在函数,类经常可见,
2.局部作用域中的变量,其使用范围不会超过本函数或类
3.也称之为本地作用域local
#局部变量
def fn0():
x=10
return x
def fn1():
print (x)
print (x)
#name 'x' is not defined
#全局变量
x = 10
def fn0():
return x + 1
fn0()
#返回值???
3. 函数嵌套
def fn0():
def too():
print ('sum')
too()
print ('fn0')
fn0()
too()
#NameError: name 'too' is not defined
内部的函数不知能直接在外部使用,否则会抛出NameError异常,以为他在函数体外并不可见.
其实too就是一个标识符,函数名字而已,一个fn0函数内定义的一个变量而已.
1.嵌套解构的作用域
def other1():
f = 100
def too():
print ('too',f,chr(f))
too()
print ('other',f,chr(f) )
other1()
#other1 返回 too 100
#too 100 d
#other 100 d
def other2():
f = 200
def too():
f = 201
print ('too',f,chr(f))
too()
print ('other2',f,chr(f))
other2()
#other2 返回函数内部调用f = 201
#too 201 É
#other2 200 È
总结: 经此发现内部函数f = 201的作用域只在too函数中,外部是不可调用的.
如果嵌套函数外层定义一个f = 201,内层还有一个相同的变量名称,那么函数有优先调用本函数内的变量.
内建函数 | 函数签名 | 说明 |
---|---|---|
chr | chr(i) | 通过Unicode编码返回对应字符 |
ord | ord(i) | 通过字符获得对应的unicode |
print (ord('中'),hex(ord('中')),'中'.encode(),'中'.encode('gbk'))
#20013 0x4e2d b'\xe4\xb8\xad' b'\xd6\xd0'
chr(20013) #'中'
chr(97) #'a'
4.赋值语句的问题
x = 10
def fn():
print (x)
fn()
#执行正常
def fn1():
#y = x + 1
x += 1
print (x)
fn1()
#local variable 'x' referenced before assignment
#代码的执行顺序为 先左后右 x 先赋值加1,在赋值左边,由于本函数中还没有引入全局变量此时fn1不知道x的数值
#多个返回值
分析: y = x + 1 是不报错的, x += 1 才报错而 x += 1 == x = x + 1 ,此时函数运行先运行左侧,函数全局变量是不能被赋值的.
5.Global 全局变量
解决python赋值语句问题
#输出结果??
x = 100
def fn():
global x
print (x)
x += 1
print (x)
fn()
#输出什么
#经测试发现,global引入全局变量,如果再次赋值?
def fn():
global x
x = 100
print (x)
x += 1
print (x)
fn() #会输出什么?
当在函数fn中定义引入全局变量x,此时fn函数将x变量使用外部变量的x.
即使fn函数中写了x = 100,也不会在fn函数中调用,x既不是局部变量从而现在即使全局变量.
总结:
x += 1 出现错误原因,先引用后赋值,而python是赋值后才算定义,即为x = x+1,此时x并没有赋值才是报错关键,解决办法:在这语句前加入x = 0 之类的赋值语句,或者通过global定义变量.
当x = 10 定义在函数内,那么即为定义为内部变量,但是一旦引用global全局变量再次赋值那么即为定义全局变量.
global 总结:
1.外部赋值变量可以在内部调用,但是函数目的就是为了封装,尽量不要使用.
2.如果函数需要使用外部变量,则使用形参然后调用实参解决.
6.闭包
def fn0():
c = [0]
def fn1():
c[0] += 1
print (c)
return c[0]
return fn1
foo = fn0()
print (foo(),foo())
c = 100
print (foo())
#line 4 行 报错问题,不会报错fn0函数已经定义c的值.
#line 9 输出 1 2
#line 11 输出 3
#global 全局变量的c 为什么没有被fn1 函数引用?
#因为fn1的c变量引用的是fn0中的c变量赋值,所以全局变量c不生效.
闭包概念,是指在嵌套函数中内层函数引用到外层函数的自由变量,由此形成闭包.
def fn0():
global count
count = 0
def fn1():
global count
count += 1
return count
return fn1
foo = fn0()
print (foo(),foo())
此函数中使用global ,引入全局变量不在使用闭包,使用引入全局变量
7.nonlocal
宣告不是本地变量,而是函数体外的变量
def fn0():
count = 0
def fn1():
nonlocal count
count += 1
return count
return fn1
foo = fn0()
print (foo(),foo())
使用nonlocal 引入fn1函数体外的count赋值.
8.函数销毁
定义一个函数就是生成一个函数对象,函数名只想函数对象.
1.使用的del删除函数,使其连接数-1.
2.使用相同函数名的函数进行覆盖,使其连接数-1.
3.python函数体结束时,函数会自动销毁.
函数也是对象,也不例外,是否销毁,还需要看引用数量.
9.变量名解析原则: LEGB
loacl: 本地作用域,局部作用域local命名空间.
Enclosing,python2.2是引入嵌套函数实现闭包.
Global,全局作用域,一个模块的命名空间,模块被import创建,函数退出即消失.
build-in,内置模块名称,生命周期从python解释器创捷到退出即消亡.
解析原则:LEGB 首先查找本地变量,查找全局变量,查找内置模块.
寻找顺序
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8X9b9F4z-1652930911435)(C:\Users\Administrator.DESKTOP-TNPJOM3\AppData\Roaming\Typora\typora-user-images\image-20220505105704287.png)]
内置函数,内建函数
迭代器:
内建函数 | 函数签名 | 说明 |
---|---|---|
inter | inter() | 将一个可迭代对象包装成迭代器 |
next | next() | 去迭代器下一个元素 如果取完则会爆出 |
reversed | reversed(seq) | 返回一个反转的迭代器(递归) |
enumerate | enumerate(seq,start=0) | 迭代一个可迭代对象,返回一个迭代器,每一个元素都是数字和元素 |
针对特殊对象一定是可迭代的,
通过inter的方式把一个可迭代对象封装成一个迭代器
通过next方式获取迭代器的下一个元素
生成器对象就是迭代器对象,但是迭代器不一定是生成器对象
生成器 :
print (“这就是专业”)
def fn () 函数
for i in 循环
nun
迭代对象
能够通过迭代送出一个一个不同的元素
所谓相同是判断容器是否相同,而不是值相同,比如说[‘a’,’a‘],值是相同的,但是在这个列表中属于两个不同的元素.
可以迭代对象是未必有序的,未必可以索引的
可迭代对象: list列表,tuple元组, string字符串,bytes,bytearray,set集合,dict字典,生成器迭代器
使用成员操作符,in not in判断迭代器是否存在元素
lit = [*range(10)]
it = iter(lit)
for i in range(10):
print (next(it))
for k,y in enumerate(it,2):
print (k,y)
for x in reversed(lit):
print (x)
print (1 in it )
print (1 in lit)
#如果出现取完状况会怎么样?
#最后判断布尔值是什么?
四、匿名函数
1.lambda表达式
python中使用lambda表达式构建匿名函数
使用lambda 格式: lambda [参数 ]:表达式
参数列表不需要小括号,无参数就不写参数
冒号用来分隔返回参数和表达式
不需要使用return,表达式的值,就是匿名函数返回的值,表达式中不能出现等号
lambda表达式(匿名函数)只能写在一行上,称之为单行函数
lambda x,y : x+y
#等于
def fn(x,y):
return x+y
lambda x,y :(x+y) **2
#等于
def fn2(x,y):
return (x+y) **2
print (lambda(x:100)(1))
print (lambda (x:x)(10))
#配置缺省值
print ((lambda x,y=10 : x+y)(12) )
print ((lambda x,y=10:x+y)(10,20))
#keyword-only
print ((lambda x,*,y=5:x+y)(1))
print ((lambda x,*,y=10:x+y)(10,y=10))
#可变形参
print ((lambda *args:[for x in args])(*range(10)))
#需求,构建一个字典,所有key对应的值是一个列表,创建新的kv对的值也是空列表
d = {c:[] for c in 'abcde'}
d['a'].append(10)
d['v'].append(100)
x = ['a','b','c','d','e','f']
print (sorted(x,key=str))
x = ['a','b','c','d','e','f']
print (sorted(x,key=str))
x = ['a', 1, 'b', 20, 'c', 32]
print (sorted(x,key=lambda x : x if isinstance(x,int) else int(x,16)))
#isinstance判断整数
#int 判断16进制
五.生成器函数
python中2中构造生成器对象
1.生成器表达式
2.生成器函数
函数体中带有yield语句的函数体
与不同函数调用不同,生成器函数调用返回的是生成器对象.
m = (i for i in range(100))
print (next(m))
print (type(m))
print (next(m))
print ('*' * 10)
def coo():
for i in range(10):
yield i
print (coo())
print (next(coo()))
print (type(coo()) )
g = coo()
for i in g :
print (i)
总结:
生成器函数可以多次yield 但是每执行一次yield就会暂停一次,把yield的返回值返回.
再次执行会执行到下一个yield语句再次输出返回值.
函数返回:
return语句依然可以终止函数,但是无法输出返回值.
return会导致当前函数返回,无法继续运行,也无法获取下一个值抛出stopiteration异常.
如果函数没有显示return语句,如果生成器函数执行到行尾(相当于执行returnnone ),一样会抛出 stopiteration异常.
1.生成器函数
包含yield语句的生成器函数被调用之后,生成生成器函数的时候,生成器函数的函数体不会立即执行.
通过next会从函数的当前位置向后执行一个yield语句,会弹出值并暂停函数执行.
再次调用next会执行上方操作.
继续调用next直到郑承气函数结束执行了,会抛出stopiteration语句.
1.无限循环
def count():
c = 0
while True:
c += 1
yield c
i = count()
print (next(i))#1 此时i = 0 next 1
print (next(i))#2 此时i = 2 next 3
print (next(i))#3 此时i = 3 next 4
print (next(i))#4 此时i = 4 next 5
print (next(i))#5 此时i = 5 next 6
print (next(i))#6 此时i = 6 next 7
#
2.计数器
def inc():
def count():
c = 0
while True:
c += 1
yield c
i = count()
return next(i)
print (inc())#1
print (inc())#1
print (inc())#1
#分析打印结果
#在函数体内部进行next和赋值,导致i每次都是等于0,0的next 为1
#修改般
def inc():
def count():
c = 0
while True:
c += 1
yield c
i = count()
def inner():
return next(i)
return inner
# return lambda : next(i)
foo = inc()
print (inc())
print (foo())
print (foo())
print (foo())
#返回值?
#函数如何执行?
#首先 foo = inc(),当调取函数foo()时开始运行 函数体inc(),return inner会next(i),i变量等于 count(),就会执行函数体 count()执行完后会弹出返回值1,那么再次赋值i=1,next1则为1 2 3 4.
3.斐波那契数列
#斐波那契数列原理 a,b,a+b
def fib():
a = 0
b = 1
while True :
yield b
a , b = b , a + b
f = fib()
for i in range(1,102):
print (i,next(f))
#采用lambda解决
lambda a=0,b=1: (yield b while True a,b = b ,a + b)
def fib():
a = 0
b = 1
while True:
yield b
a, b = b, a + b
f = fib()
# f = (lambda a=0,b=1 : b while True a , b = b ,a + b) 正确吗?
for i in range(1, 10):
print(i, next(f))
4.协程
生成器的高级用法
相比进程,线程更加的轻量级,是在用户空间调度函数的一种实现
python3 asyncio就是协程实现,已经加入标准库.
python3.5 使用async,await关键字直接原生支持协议
协程调度实现
生成A和B
使用next(A)之后,A实行到了yield语句暂停,再去执行next(B),B执行到yield语句也暂停,然后再次调用next(A),在调用next(B)周而复始.实现调度效果.,
可以引入调度的策略实现切换的方式
协程是一种非抢占方式
5.yield from 语法
python3.3 增加了 yield 的from语法,使yield form iterable 等价于for item in
iterable: yield item.
yield from 语法演示
def count():
for i in range(100):
yield i
def fn():
yield from range(100) #函数出现yield 也是生成器函数
fn1 = count()
fn2 = fn()
print (next(fn1))
print (next(fn2))
print (next(fn1))
print (next(fn2))
print (next(fn1))
print (next(fn2))
print (next(fn1))
print (next(fn2))
print (next(fn1))
print (next(fn2))
6.作业 做出map函数
六、递归函数
1.函数执行过程
C语言中函数的活动和栈有关.
栈是后进先出的数据解构.栈是由低端向顶端生长,栈顶加入数据称之为压栈,入栈,栈顶弹出数据称之为出栈.
def fn() :
i = x + y
return i
print (i)
def fn1() :
a = 1
b = fn(a,2)
return b
fn1()
函数实行过程:
理解概念 : 栈堆是存放数据 比如 a = 1 1存放在栈堆
栈帧是存放变量和函数, 比如a = 1 a 存放栈帧 调用add 函数 add存放在栈帧,如何关联? 比如fn1函数中 a = 1
首先 fn1 函数压入栈帧, 栈帧中加入a 指向栈堆中的1
第二行 b = fn(a,2),等式先做右边,在栈帧中创建add函数并且创建变量x,y x指向 1 ,y 指向 2 .
在栈中计算fn函数结果为3保存在栈中,将栈帧中的b指向栈堆中的3.
print在栈帧中创建将i压入栈帧.
print函数执行完毕,函数返回,移除栈帧,
add函数调用返回,移除栈帧.
fn1栈帧中增加b指向fn的函数返回值对象,
移除fn1函数栈帧.
问题:
如果再次调用函数会怎么样?
如果再次调用那么会再次在栈帧中创建函数,并且关联栈堆.
每次调用都是独立的.
2.递归
1.定义
函数直接或者间接调用自己视为递归.
递归需要有边界条件,递归前进段,递归返回段.
递归一定要有边界条件.
当边界条件不满足时,递归前进.
当边界条件满足时,递归返回.
#F(1)=1, F(n)=F(n-1)+F(n-2)
def fv_v1(n):
a = b = 1
for i in range(n-2):
a , b = b , a+ b
return print (b)
fib1 = fv_v1(100)
print (fib1)
#递归实现:
def fv_v2(n):
if n < 3:
return 1
return fv_v2(n-1) +fv_v2(n-2)
fib2 = fv_v2(10)
print (fib2)
#递归调取很慢
def fv_v3(n):
return 1 if < 3 else fv_v3(n-1) + fv_v3(n-2)
fib3 = fv_v3(10)
print (fib3)
通过观察递归的斐波那契数列发现重复计算非常多,比如计算fib3 ,需要计算fib1和fib2
当计算fib4时又要计算fib3和fib2,重复计算了一次,那么当你fib层数越多就会越慢这就是类似垃圾代码
2.递归要求
递归一定要有终止条件,不可以做无休止递归.如果没有退出或者终止条件那就是无限调用.
python针对递归有深度限制,以保护解释器.
超过深度递归,抛出 RecursionError: maximum recursion depth exceeded
3.递归效率
使用timeit进行测试,两个版本的斐波那契额数列的效率,
递归明显效率低,能否改进fb_v2函数?
#递归效率
def fv_v2(n):
if n < 3:
return 1
return fv_v2(n-1) + fv_v2(n-2)
fib2 = fv_v2(10)
print (fib2)
def fib_v3(n,a=1,b=1):
if n < 3 :
return b
a , b = b , a+b
return fib_v3(n-1, a, b )
fib3 = fib_v3(6)
print (fib3)
#n=结束条件!
#每递归一次n-1,直到n=1
#第一次
def fib_v3(6,a=1,b=1):
if 6 < 3 :
return b
a , b = b , a+b
return fib_v3(6-1, 2, 1 )
fib3 = fib_v3(6)
print (fib3)
#第二次
def fib_v3(6,a=2,b=1):
if 5 < 3 :
return 1
2 , 1 = 1 , 3
return fib_v3(5-1, 1, 3 )
fib3 = fib_v3(6)
print (fib3)
#第三次
def fib_v3(4,a=1,b=3):
if 4 < 3 :
return 3
1 , 3 = 3 , 4
return fib_v3(4-1, 3, 4 )
fib3 = fib_v3(6)
print (fib3)
#第四次
def fib_v3(3,a=3,b=4):
if 3 < 3 :
return 4
3 , 4 = 4 , 7
return fib_v3(4-1, 3, 4 )
fib3 = fib_v3(6)
print (fib3)
#思考调用多少次
#思考递归计算多少次
#整体调用此时为n-2 次数 == 3 时就return b
4.间接递归
def fn1():
print (1)
fn2()
def fn2 ():
print (2)
fn1()
fn1()
#RecursionError: maximum recursion depth exceeded while calling a Python object
递归函数,通过其他函数进行调用自己,一样可以时间递归.
只要牵扯到递归函数,都需要谨慎好边界问题.
5.总结
递归是一种很自然的表达,符合逻辑思维.
递归运行的效率较低,每一次调用都需要重新加载栈堆和栈帧.
递归有深度限制,这是python的自我保护,如果深度太深,就会造成内存溢出了.
请使用有限次数的递归,可以使用递归多次调用,尽量使用循环代替.
大部分的递归都可以使用循环代替.
即使代码很整洁,能不用递归就不要用递归.
七、 可迭代对象和内建函数
1.可迭代对象
内建函数 | 函数签名 | 说明 |
---|---|---|
inter | iter(iterable) | 把一个可迭代对象包装成迭代器 |
next | next([iterable],(default)) | 取迭代器下一个元素,如果迭代器无元素则抛出stopiteration异常 |
reversed | reversed(seq) | 返回一个翻转的元素 |
enumerate | enumerate(seq,start=0) | 迭代一个可迭代对象,返回一个迭代器,迭代器返回由数字和元素组成的二元组 |
迭代器定义
特殊的对象,一定是可迭代的对象,具备可迭代对象的特征
通过iter把一个可迭代对象保证成迭代器
通过next获取迭代器的下一个对象
生成器对象,就是迭代器对象,但是迭代器未必是生成器
可迭代对象定义:
能够通过迭代一次一次返回不同的元素的对象.
判断是否是不同的元素,比如列表里的值可以是相同的,[‘A’,’A’],但是每次迭代都是不同的元素.
可以迭代未必有序,未必可索引
可迭代对象有 list 列表,tuple 元组,dist 元组,string字符串,bytearray字节,range,set集合,生成器,迭代器.
判断成员可以使用 is ,not is 判断元素的是否存在
is 是遍历,时间复杂程度为O(n).
2.sorted 排序
lit1 = [*range(10)]
it = iter(lit1)
# print (next(it))
# lit2 = sorted(lit1,key=lambda x:1-x)
# print (lit2)
lit1.sort(key=lambda x: 6-x ) #通过lit1.sort 直接修改列表
print (lit1)
#默认sorted的key为none,正排序,当为复数为倒叙.
3.过滤
过滤是遍历过程,时间复杂度O(n)
针对可迭代对象进行遍历,返回一个迭代器
function参数是一个参数的函数,且返回值应当是布尔类型,或者等效bool值.
function如果参数是none,可迭代对象每一个元素等效bool.
l1 = list(filter(lambda x:x%3==0,[1,3,4,5,7,8,9,-3,78,28,103]))
l2 = list(filter(None,range(10)))
l3 = list(filter(None,range(-10,10)))
print (l1)
print (l2)
print (l3)
function默认为删选条件,基本语法filter(function,iterable)
4.map映射
定义map(function,*iteables) ->map object
对多个可迭代对象的元素,按照指定函数进行映射.
返回一个迭代器
l4 = list(map(lambda x:2*x+1,range(100)))
d1 = dict(map(lambda x:(x%5,x) , range(500)))
d2 = dict(map(lambda x,y:(x,y) ,'abcdef',range(199)))
print (l4)
print (d1)
print (d2)
5.拉链函数ZIP
zip(*iterables)
像拉链一样,把多个可迭代对象合并在一起,返回一个新的迭代器.
将每次从不同对象取到的元素合并到一个迭代器
z1 = list(zip(range(100),range(200)))
z2 = dict(zip(range(100),range(200)))
z3 = {str(x):y for x,y in zip(range(10),range(20)) }
print (z1)
print (z2)
print (z3)
八、高阶函数
1.一等公民
函数在python中是一等公民 first-class object
函数也是一个对象,一个可调用对象
函数可以作为一个普通变量,也可以作为函数的参数,返回值
2.高阶函数
高阶函数(high - order function)
数学概念 y=f(g(x))
在数学和计算科学中,高阶函数应当满足以下至少一个条件
1.接受一个或多个函数作为参数
2.输出一个函数
案例:
def count(base):
def inr(step=1):
base += step
return base
return inr
#观察函数是否有问题
# inr函数会找不到base变量,因为python会先做等式右侧 等式右侧是 step + base其中base是不存在的.
def count(base):
def inr(step=1):
nonlocal base
base += step
return base
return inr
a = count(10)
print (a)
#需要加入到nonlocal,表示这个变量不是本函数变量需要往上层函数寻找此变量,他跟global区别在于,global是表明是全局变量,而nonlocal 声名不是全局变量而是上层函数的变量.
global :声名变量是全局变量,让函数寻找全局变量进行调用
nonlocal:声名变量是局部变量,让函数往上层函数调用,而不是寻找全局变量调用.
def count(base):
def inr(step=1):
nonlocal base
base += step
return base
return inr
a = count(10)
print (a)
fn1 = count(10)
fn2 = count(10)
print (fn1, fn2)
print (fn1 == fn2) #??
print (fn1 is fn2 )#??
#分析函数 首先函数每次运行都需要在内存创建一个新的内存地址,所以fn1==fn2和fn1 is fn2 是一个东西,他们的内存地址是不同的
3.柯里化
柯里化是什么?
是让一个函数接受另一个函数的返回值作为自己的参数进行执行.
官方一点就是:将原来接受两个参数的函数,变成新的接受一个参数的函数的的过程,新的函数返回一个以原有第二个参数为参数的函数.–有点绕
z = f(x,y) 转换成 z= f(x)(y)的形式
例子:
def add(x,y):
return x + y
原本的函数调用,即为add(4,5),那么现在变成add(x)(y)
每一个()表示调用一次,那么双()表示调用两次
add(x,y)
#== 等价于
t = add(4)
t(5)
那么通过嵌套函数是否可以实现:
def add(x):
def _add(y):
return x + y
return _add
add(100,200)#这样可以吗?
#add函数只接受一个传参,这样会报错的
add(100)(200)
#这样他才会接受此参数,通过print 进行打印
ad = add(100)(200)
print (ad)
通过嵌套函数是不是可以这样
def add(x,y,z):
return x + y + z
#add(100,200,300) #可以吗
ad = add(100,200,300)
ac = add(200)(100)(300)
aa = add(300,200)(300)
ae = add (100)(200,400)
print (aa,ab,ac,ad)
#这其中有吗
4.装饰器
回顾:
为一个加法添加自己的记录实参:
def add(x,y):
print ('Sum.Number_{}+{}={}'.format(x,y,x+y))
return x + y
ad = add(10,20)
print (ad)
首先我们这里是实验,加法在日常中使用率非常平常,add加法属于业务功能而print输出为输出信息功能非功能代码,好的办法的是要把她分离.
思路案例:
def add(x,y):
return x + y
def logger(fn):
print ('+' * 30 )
ret = fn(100,200)
print ('-' * 30 )
return ret
print (logger(add))
这样是不是就分离开了,将一个函数拆分成两个函数,但是还是不够好,能够将位置传参和关键字传参都加入?
def add(x,y):
return x + y
def logger(fn,*args,**kwagrs):
print ('+' * 30 )
ret = fn(*args,**kwagrs)
print ('-' * 30 )
return ret
print (logger(add,200,300))
#分析函数调用,首先logger是接受函数,关键字传参,可变位置传参的,然后再调用函数针对加法函数名可以自己diy,
想一想刚才的柯里化,函数嵌套,那么这个可以用吗?
案例:
def add(x,y):
return x + y
def logger(fn):
def wrapper(*args,**kwargs):
print ('-'* 30)
ret = fn(*args,**kwargs)
print('*' * 30 )
return ret
return wrapper
#第一种调用方法
ad =logger(add)
it = ad(400,500)
print (it)
#第二种调用方法
print (logger(add)(100,200))
#第三种
add = logger(add)
print (add(400,900))
#分析代码
#第一种方法,首先针对logger传入add参数赋值给ad,然后再通过位置传参传入400,500函数整体执行流程: 首先logger 接收到add,会调用add函数随后进入wrapper接收到add后边的两个位置传参,400和500打--然后进入到第七行先做等式右边fn=add,位置传参400 500函数计算结果被赋值到ret,输出*** 通过return ret将结果弹出,然后外层函数logger把warpper 函数弹出.
#第二种类似
#装饰器
def logger(fn):
def warpper(*args,**kwargs):
print ('*' * 10)
ret = fn(*args,**kwargs)
print ('-'* 30 )
return ret
return warpper
@logger # add = logger(add) | add == warpper
def add(x,y):
return x + y
print (add(200,300))
1.无参装饰器
上例语法即为装饰器语法,即为午餐装饰器
@符号后是一个函数
虽然是无参装饰器,由于@的是一个函数也是一个单参函数
上列的logger是一个高阶函数.
2.日志记录装饰器的实现
#引入time 模块
import time
import datetime
def logger(fn):
def wapper(*args,**kwargs):
print ('+++++++1')
start = datetime.datetime.now()
print (start)
ret = fn(*args,**kwargs)
print ('------+')
delta = (datetime.datetime.now() - start).total_seconds()
print (datetime.datetime.now())
print ('function {} to {} '.format(fn.__name__,delta) )
return ret
return wapper
@logger
def add(x,y):
time.sleep(2)
print (' x + y ')
return x + y
#调用
print ((add)(10,20))
3.装饰器本质
可以理解为外包分段加强
我需要一个东西,这个东西需要很多步骤才能生产而我只会其中一部分,我需要调用或者外包给其他人给我实现这个功能,而我是这个东西的主人,表面上谁也看不出来.
4文档字符串
python中的文档字符串documentation striges
在函数第一行往往都是描述文本,且习惯是多行文本,一般使用三引号进行表示
文档字符串的语法也是符合语法的
一般来说第一行概述,空一行第三行描述,首字符大写因为
使用function.__ doc __ 进行访问.
我们拿之前的装饰器函数测试一下
import time
import datetime
def add(x,y):
"""Is a add function
input x , y return sum """
time.sleep(2)
print (' x + y ')
return x + y
print (add.__doc__,add.__name__)
#返回什么?
import time
import datetime
def logger(fn):
"""wapper doc """
def wapper(*args, **kwargs):
print('+++++++1')
start = datetime.datetime.now()
print(start)
ret = fn(*args, **kwargs)
print('------+')
delta = (datetime.datetime.now() - start).total_seconds()
print(datetime.datetime.now())
print('function {} to {} '.format(fn.__name__, delta))
return ret
return wapper
@logger
def add(x, y):
"""Is a add function
input x , y return sum """
time.sleep(2)
print(' x + y ')
return x + y
print((add)(5, 7))
print ('doc=={},name=={}'.format(add.__doc__,add.__name__))
#这个又返回什么?
#仔细想一下为什么?怎么解决嘞?
首先分析函数,这里add函数等式是== add = logger(add),也可以理解为 add =wapper
那么当add函数被调用他会将add函数传入logger的fn中然后再传入wapper,再wapper里进行工作,可以理解为logger函数提供了一个叫wapper的装饰器函数他是一个update_wapper就是一个属性赋值函数.
装饰器函数参数wraps包括:
wrapped : 就是被包装的函数
wrapper:包装函数
assigned = 元组覆盖属性__module__ , name , qualname , doc , __annotations__对应
模块名、名称、限定名、文档、参数注解
update 用被包装函数的属性覆盖包装函数的同名属性
在上述函数中,就需要使用update,那么add就会使用wapper的doc属性.
如下使用:
import time
import datetime
from functools import wraps
def logger(fn):
"""logger doc """
@wraps(fn) #用被包装的函数名覆盖包装函数的同属性名称.就是将wapper替换为add,那么调用doc时就会调用doc了
def wapper(*args, **kwargs):
"""wapper doc """
print('+++++++1')
start = datetime.datetime.now()
print(start)
ret = fn(*args, **kwargs)
print('------+')
delta = (datetime.datetime.now() - start).total_seconds()
print(datetime.datetime.now())
print('function {} to {} '.format(fn.__name__, delta))
return ret
return wapper
@logger
def add(x, y):
"""Is a add function
input x , y return sum """
time.sleep(2)
print(' x + y ')
return x + y
print((add)(5, 7))
print ('doc=={},name=={}'.format(add.__doc__,add.__name__))
5.带参装饰器
@之后bu是一个单独的标识符,是一个函数调用
函数调用的返回值又是一个函数,此函数是一个无参装饰器
带参装饰器,可以有任意函数
@function(1)
@function(1,2)
@function(1,2,3)
import time
import datetime
from functools import wraps
def logger(fn):
"""logger doc """
@wraps(fn)
def wapper(*args, **kwargs):
"""wapper doc """
print('+++++++1')
start = datetime.datetime.now()
print(start)
ret = fn(*args, **kwargs)
print('------+')
delta = (datetime.datetime.now() - start).total_seconds()
print(datetime.datetime.now())
print('function {} to {} '.format(fn.__name__, delta))
return ret
return wapper
@logger
def add(x, y):
"""Is a add function
input x , y return sum """
time.sleep(1)
print(' x + y ')
return x + y
@logger
def sum(x,y):
"""Is a sum function"""
time.sleep(1)
return x * y
print ('doc=={},name=={},return=={}'.format(add.__doc__,add.__name__,add(100,200)))
print ('doc=={},name=={},return=={}'.format(sum.__doc__,sum.__name__,sum(10,20)))
#思考问题
#logger什么时候执行?
#logger执行过几次?2
#wraps装饰器执行过几次?2
#wrapper的__name__ 等属性被覆盖过几次? 2
#add.__name__ 打印什么名称? add
#sub.__name__ 打印什么名称? sum
5.函数注解
1.注解
python是动态语言,变量随时被赋值改变类型,python变量是随运动决定的.
def add(x,y):
return x + y
print (add(1,2))
print (add(1,x))
print (add(1,'x'))
print (add(1,'100'))
print (add([1],[2]))
print (add('test','one'))
#如果函数这样传入参数,还能正常运行吗? 发现不到运行的时候是不报错的
动态语言的缺点:
难发现: 不能做执行前检查,到了运行时才能暴露出来.
难使用: 函数使用者看到函数时,无法知道函数设计者的意图,如果没有参考文档,是无法使用的,当拿到一个新的api使用者不知道如何使用.
动态类型对类型约束不强,小规模开发危害不大,使用深度和开发强度越大缺点越明显.
如何解决?
函数字符串,对函数对类对模块能够详细的进行描述,局里让使用者使用帮助就能知道使用方法,但是大多数项目管理不严格可能文档不全,或项目开发后没有及时的更新.
类型注解:函数注解,变量注解
def add(x:int, y:int) -> int:
"""
:param x: int
:param y: int
:return: int
"""
return x + y
a1 = add('10','20')
a2 = add(10,20)
a3 = add('kkk','gogo')
print (add.__name__,add.__doc__,add.__annotations__,sep='\n')
print (a1,a2,a3)
#观察代码运行
函数注解:
3.15版本接入
对函数的形参和返回值类型说明
只是对函数返回值和形参进行说明,是辅助说明,并非强制类型约束
第三方工具 vscode 或者pycharm可以进行代码分析发现隐藏bug
函数注解放在函数__annotations__中,字典类型
2.类型注解
i:int = 3
y:int = 10
k:str = 'abc'
f:str = 'asv1'
print (i,y,k,f)
3.5版本引入
对变量类型说明,非强制约束
第三方工具进行类型分析和推断
3.类型检查
函数传参报错 如何检查?
再函数内部使用isinstance来判断参数类型是否正确,检查并非业务代码,但是算不算是代码入侵,如何检查更加的灵活?
非侵入式代码
动态获取待检查函数的参数类型注解
当函数传入实参时,和类型注解对比
能够使用函数__annotations__属性?python3.6之后字典记录录入序,但是我们还是认为字段是无序的,那如何按照位置传参?
inspect模块
inspect模块
inspect.isfunction(add) 判断是否为函数
instpect.ismethod(pathlib.path().absolute) 是否是类方法 要绑定
inspect.igeneratir(add()) 是否为生成器对象
inspect.isgeneratorfunction(add) 是否是生成器函数
inspect.isclass 是否是类
inspect.ismodule(inspect)是否是模块
inspect.isbuiltin(print) 是否是内建函数
还有很多需要查询时自行查询
inspect.signature(callable,*,follow_wapped=true)
获取可调用对象的签名
3.5增加follow_wapped,如果functool的warps或者update_wapper.follow_wapped为true那么会跟进函数的wrapped,获取函数真正的签名.
import inspect
def add (x:int,/,y:int=5,*args,m=6,n,**kwagrs) ->int:
return x + y + m + n
sig = inspect.signature(add)
print (sig)
print (sig.return_annotation)
params = sig.parameters
print (type(params))
print (params)
for k,v in params.items():
print (type(k),k,type(v),v,sep='\n')
1.ipsect.parameter
四个属性,
name 参数名,字符
default 缺省值
annotation 注解类型
kind类型
positional_only 只接收位置传参
positional_or_keyword 可以接受关键字传参和位置传参
var_positional 可变位置参数 队形*args
keyword_only 对应* 或者*args之后的出现的可变关键字形参,只接受关键字传参.
var.keyword 可变关键字参数,对应**kwargs
empty 特殊类,标记default 和annotation 为空
import inspect
def add (x:int,/,y:int=5,*args,m=6,n,**kwagrs) ->int:
return x + y + m + n
sig = inspect.signature(add)
print (sig)
print (sig.return_annotation)
params = sig.parameters
print (type(params))
print (params)
for k,v in params.items():
print (type(k),k,sep='\n')
t:inspect.Parameter =v
print (t.name,t.default,t.annotation,t.kind,sep='\n')
//C:\Users\Administrator.DESKTOP-TNPJOM3\AppData\Roaming\Typora\conf\conf.user.json修改default字段
4.参数类型检查
def add( x:int ,y:int = 10 ) -> int:
return x + y
a1 = add('abc','xyz')
a2 = add(4,6)
print (a1,a2)
分析:
调用时用户才会传入实参,才能判断实参是否符合类型要求
调用时,让用户感觉上还是调用原函数
如果类型不符合提示用户
装饰器!!!
import inspect
def add( x:int ,y:int = 10 ) -> int:
return x + y
def check(fn):
sig = inspect.signature(fn)
params = sig.parameters
for k,v in params.items():
print (k,v,v.default,v.annotation)
check(add)
add(4,6)
装饰器版本:
import inspect
from functools import wraps
def check(fn):
@wraps(fn)
def wrapper(*args,**kwargs):
sig = inspect.signature(fn)
params = sig.parameters
for k,v in params.items():
print (k,v,v.default,v.annotation)
ret = fn(*args,**kwargs)
return ret
return wrapper
@check
def add( x:int ,y:int = 10 ) -> int:
return x + y
print (add(4,5))
如何按照位置传参:
import inspect
from functools import wraps
def check(fn):
@wraps(fn)
def wrapper(*args,**kwargs):
sig = inspect.signature(fn)
params = sig.parameters
print (params)
print (args,kwargs)
values = tuple(params.values())
for i,v in enumerate(args):
if values[i].annotation is not values[i].empty and isinstance(v,values[i].annotation):
print ('{} = {} is ok '.format(values[i].name,v))
ret = fn(*args,**kwargs)
return ret
return wrapper
@check
def add( x:int ,y:int = 10 ) -> int:
return x + y
print (add(4,5))
按照关键字传参
import inspect
from functools import wraps
def check(fn):
@wraps(fn)
def wrapper(*args,**kwargs):
sig = inspect.signature(fn)
params = sig.parameters
print (params)
print (args,kwargs)
values = tuple(params.values())
for i,v in enumerate(args):
if values[i].annotation is not values[i].empty and isinstance(v,values[i].annotation):
print ('{} = {} is ok '.format(values[i].name,v))
for k,v in kwargs.items():
if params[k].annotation is not inspect.__empty and isinstance(v,params[k].annotation):
print ('{} = {} is ok'.format(k,v))
ret = fn(*args,**kwargs)
return ret
return wrapper
@check
def add( x:int ,y:int = 10 ) -> int:
return x + y
print (add(4,10))
九、functools模块
1.reduce
function.reduce (function,iterable[,initial])
减少的意思
初始值没有提供就在可迭代对象取一个
from functools import reduce
s = sum(range(10))
#s = reduce(lambda x :x ,range(100))
#<lambda>() takes 1 positional argument but 2 were given
#s = reduce(lambda x,y :print(x,y) ,range(10))
s = reduce(lambda x,y : x + y ,range(10),100)
print (s)
reduce需要返回两个参数
而通过测试发现,reduce会将上一次lambda的返回值作为x继续使用,
sum只能算加法,而reduce能算更复杂的算法
那么5的阶乘怎么算?
from functools import reduce
s = reduce(lambda x,y : x * y ,range(1,6) ,1 )
print (s)
#修改版
from functools import reduce
print ('input 1 up ,is 0 quit')
while True :
n = int(input(">>>"))
if n == int(0) :
break
s = reduce(lambda x,y : x * y ,range(1,n+1) ,1)
print (s)
elif n:
s = reduce(lambda x,y : x * y ,range(1,n+1) ,1)
print (s)
2.partial 偏函数
偏函数
将函数的部分参数进行固定,相当于为部分参数添加了固定值,形成一个新的函数,并且返回这个函数,.
新函数就是对原函数的封装
from functools import partial
import inspect
def add(x,y):
return x + y
newadd = partial(add,y=6)
print (newadd(4))
print (newadd(4,y=10))
print (newadd(x=24,y=10))
#print (newadd(1,20))
print (newadd(1,y=20))
print (inspect.signature(newadd))
#----------------------------------------
def add(x,y,*args):
return x + y +sum(args)
newadd = partial(add,1,2,3,4,5)
print (newadd)
print (newadd(10))
# print (newadd(x=1,y=2))
print (inspect.signature(newadd))
偏函数本质:
from functools import partial,wraps
import inspect
def add(x,y,z):
return x +y + z
newadd = partial(add,x = 1,y = 2,z = 3)
print (newadd)
print (inspect.signature(newadd))
#类似wraps通过 将原函数的id 进行替代
3.lru.cache
缓存技术
@functools.lru_cache (maxsize=128,typed=none)
lru即是least-recently-uesrd,最近最少使用缓存,cache
maxsize 默认128,如果设置为none则缓存无限增长,当maxsize为2的幂次方效果最好.
typed如果为True那么不同的函数则会单独缓存,为none则混合缓存.
其实lru.cache 即为一个字典类似redis.
import datetime
from functools import lru_cache
import time
start = datetime.datetime.now()
print (start)
@lru_cache(128)
def add(x,y=5):
return x + y
for i in range(1000000):
n= add(i,i+1)
delete = (datetime.datetime.now() -start).total_seconds()
print (delete)
lru_cache的本质就是创建字典,在重复计算时可以体现作用
from functools import lru_cache
import datetime
start = datetime.datetime.now()
#@lru_cache(128)
def fib(n):
return 1 if n < 3 else fib(n-1) + fib(n-2)
delete = (datetime.datetime.now() -start ).total_seconds()
print (fib(100))
print (delete)
#斐波那契额数列等差测试,建议20 -30 左右
总结:
lru_cache装饰器的应用
使用前提:
1.同样的函数一定得到同样的结果,一段时间以内,同样的输入得到同样的结果,
2.计算代价高,函数执行时间长
3.需要执行多次,每一次的计算代价都很高
本质就是建立函数调用返回值的映射.
缺点:
不支持缓存过期,key无法过期失效
不支持清除操作,
不支持分布式,单机缓存
lur_cache 使用场景,单机需要空间换时间的地方,用缓存可以实现快速查询
理解lur_cache 可以方便理解 后期的redis 技术