Python函数复习

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,内层还有一个相同的变量名称,那么函数有优先调用本函数内的变量.

内建函数函数签名说明
chrchr(i)通过Unicode编码返回对应字符
ordord(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)]

内置函数,内建函数

迭代器:

内建函数函数签名说明
interinter()将一个可迭代对象包装成迭代器
nextnext()去迭代器下一个元素
如果取完则会爆出
reversedreversed(seq)返回一个反转的迭代器(递归)
enumerateenumerate(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.可迭代对象

内建函数函数签名说明
interiter(iterable)把一个可迭代对象包装成迭代器
nextnext([iterable],(default))取迭代器下一个元素,如果迭代器无元素则抛出stopiteration异常
reversedreversed(seq)返回一个翻转的元素
enumerateenumerate(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 技术

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值