0
点赞
收藏
分享

微信扫一扫

python之柯里化和无参装饰器(11)

一、柯里化定义

定义:

  指的是将原来接受两个参数的函数变成一个新的接受一个参数的函数过程。新的函数返回一个原有第二个参数为参数的函数

  z=f(x,y) 转换成z=f(x)(y)的形式变成了两个函数

 以add函数为例子,来理解柯里化

def add(x,y):
    return x+y
	
print(add(4, 5))
        
 函数传参就两种方式,一种关键字、另一种是位置参数,我的目标是把这个add函数
	变为add(4)(5)

怎么理解这个add(4)(5)?
	add(4)这里就是一个函数了,这个函数只有一个参数就是4,后面的(5)呢?
	这个也是一次函数调用,如果想运行正确,这就要求add(4)执行完,返回的是一
    个函数或者是一个可调用对象,它的参数要能接收5这个参数。

    返回一个函数,并在这个返回的函数基础上在进行调用。

	既然是返回的函数那就是高阶函数了,add(4)返回一个函数,而且这个函数是要
    能接收另一个参数的函数。

在继续看代码

#柯里化的目的是:
		def add(x):
   			def fn(y):
        	return x+y   #内部函数用到了外部函数的局部变量这就是闭包了
    	return fn     #fn 是一个函数 

		这里调用:
			add(4)(5)
			fn=add(4)   #做add(4)的时候返回fn,fn记住了这个函数
			result=fn(5)  	
	上面就是柯里化的一个过程

在继续看代码例子

def add(x,y,z):
		return x+y+z

	要满足add(4)(5,6) 这样的柯里化	
	要满足add(4)(5)(6) 这样的柯里化
	要满足add(4,5)(6)  的这样的柯里化
    
满足add(4)(5,6)的代码
def add2(x):
    def fn(y,z):
        return x+y+z
    return fn

print(add2(1)(2,3))

满足add(4)(5)(6) 的柯里化
def add3(x):
    def fn(y):
        def fn1(z):
            return x+y+z
        return fn1
    return fn

print(add3(1)(2)(3))

满足add(4,5)(6)的这样的柯里化

def add4(x,y):
	def fn(z):
		return x+y+z
	return fn

print(add4(1,2)(3))

这些功能能实现都是闭包技术的存在

二、装饰器的本质

装饰器,用来装饰的,一般来讲用来装饰函数或类。为了引出这个装饰器我们先从一个函数讲起。

def add(x,y): 
    return x+y

这个函数看起来虽然简单,但是五脏俱全。

这个函数的业务功能是加法,我们现在想增加一个功能,能不能记录一下你调用的参数
这样跟加法的功能没关系了,因为记录是记录的功能,记录并不是加法的功能,加法的
核心功能是做加法,只要能做出x+y这样加法就完成了自己的本职工作,但是我现在想
加入记录的功能,按道理来说,记录功能不算业务功能。

我们来直接加入记录功能看一下

如果直接加入:
def add(x,y):
	print('add函数它被调用了x={},y={}'.format(x,y))
    return x+y	

这样写并不好,非业务功能代码写在了业务代码中,这种就属于侵入式的,你把非业
务代码,写入到了业务代码中称为侵入式的。剥离很难,很不方便

如果换一种思维,A函数需要记录,B函数也需要记录,记录功能也不属于A和B的业务
功能,而且它是A和B通用的功能。如果有C函数也需要此功能该怎么办?
    那我们就抽取出记录功能,形成一个函数

我们继续来看代码,看看怎么抽取出记录功能:

def logger(fn): #随便那个函数fn,这里的参数是函数对象
    	print('调用前增强功能')
    	print('add函数它被调用了x={},y={}'.format(4, 5))
    	ret=fn(4,5) #add(4,5)
    	print('调用后增强功能')
    return ret
	
	print(logger(add))	

这样就能打印add函数的参数了,我们来看下完整的两个函数

看一下完整的两个函数的代码:

def add(x,y):
    return x+y	

def logger(fn,x,y): 
    print('调用前增强功能')
    print('add函数它被调用了x={},y={}'.format(x, y))
    ret=fn(x,y) #add(4,5)
    print('调用后增强功能')
	return ret

print(logger(add,4,5))	

这样一看代码很整齐,业务代码和非业务代码进行分离了

这是演化到装饰器的第一步

下面我们要解决参数问题:    
我们对logger函数进行调用需要3个参数
 logger(add,4,5)
 
如果add函数变为下面这样,变成能接收多个参数的
add(x,y,*z)
	return x+y+sum(z)

问题是在logger函数中怎么处理这些参数呢?
	在比如我想这样使用:
			logger(add,x=10,y=11)
			logger(add,10,y=11)
			logger(add,4,5,6)
    总不能给不同函数解决方案,不断的抄写他们的参数吧?  
    传参方式无非就是两种,用*args和**kwargs收集起来

    def logger(fn,*args,**kwargs)  函数头这样写
    
    logger只做记录,不用管参数对还是不对
    参数对还是错是由add函数判断
    
    我们对代码进行修改
def logger(fn,*args,**kwargs): #收集所有的位置参数和关键字参数
    print('调用前增强功能')
    print('add函数它被调用了{},{}'.format(args, kwargs))
    ret=fn(*args,**kwargs)  #前面加*是表示参数解构
    print('调用后增强功能')
    ret1 = ret
    return ret1

我们能不能对代码进柯里化一下

比如现在参数是这样
logger(add,x=10,y=11)
柯里化以后参数变为
logger(add)(x=10,y=10)

前面之关心fn,后面关心参数,我们来看柯里化后的代码
def add(x,y):
    return x+y	

def logger(fn):
    def wrapper(*args,**kwargs)
    	print('调用前增强功能')
    	print('add函数它被调用了{},{}'.format(args, kwargs))
    	ret=fn(*args,**kwargs)  #这里就是闭包 
    	print('调用后增强功能')
    	ret1 = ret
    return wrapper

这样进行柯里化之后,我们像下面这样使用是完全没问题的
logger(add)(x=10,y=10)

我们来继续看代码
newfunc = logger(add)
	怎么理解这句代码?
      把add扔给了logger参数的fn,然后logger中定义了一个新的函数wrapper
      然后把这个新的函数wrapper返回给了newfunc这个变量
      所以这个nwefunc函数现在是logger函数执行过程中创建的一个新的函数
      其实这个newfunc就是wrapper,这里的理解一定要有内存的概念,这个就是
      newfunc指向了waapper的地址。他俩本质上是指向了同一段内存空间。
 
我们接下来想拿到加法的结果,我们需要给参数了
result=newfunc(1,2,3,4),这样调用给参数可以吗?运行会报错吗?
	我们浅浅分析一下,当我们把4个参数传入的时候,解构之后传给
    add之后发现,add(x,y) add参数列表只有x和y,只能接收两个
    位置参数。所以这里会报错的。这里的报错是由add函数所引发的
    所以 
result=newfunc(1,2)这样就行了

    ret=fn(*args,**kwargs) 这里是闭包,内部函数用到了外部变量,fn是一个
    						函数作为变量的,它记住了add这个函数对象

上面清楚之后我们在继续做变化,继续看代码

newfunc = logger(add)
result=newfunc(1,2)
----------------------这两组方式调用对比
add = logger(add)
result=add(1,2)
print(result)

后面的这3行代码怎么理解呢?add函数和logger函数还是参照上面部分的代码
  先按照自己了理解:
		这个就是add是标识符接收了logger(add),函数的执行结果,logger(add)执
    行完返回的是warpper函数。
    后面result=add(1,2)这样调用就没问题的,还是执行1+2,输出的是3.
		
    这两种方式一样的,就是标识符名字不同,第二种add名字和函数add名一样
    就不容易让人看不懂了
 
我们来描述一下函数执行过程,add=logger(add) 等式先做右边,执行到
def add(x,y):这里定义了一个函数对象,这个对象在内存的地址是add1,继续
执行def logger1(fn):执行到这里时,定义了第二个函数对象同样指向了一段空间
addr2.

add=logger(add) 右边先做的话,目前logger的参数是addr1的地址空间, 这样
一调用把add1函数对象给了局部变量fn,进入到logger函数内部以后,先定义了一
个wrapper函数。wrapper函数对象又生成了一段新的内存空间称为addr3。
wrapper函数对象生成之后没有执行。而logger函数返回了这个wrapper函数。

所以add=logger(add)这句代码本质上是等价于add=wrapper[addr3].
赋值即定义add原来指向add1空间,现在add指向了add3的空间。

函数调用完 wrapper这个标识符会消亡,但是wrapper函数所指向的addr3空间
被保存下来了。因为add记住了这段空间.

内部函数用到外部函数变量fn就是闭包,把fn对应的函数对象,绑定在wrapper上了

也就是addr3这个对象的属性上,记录着闭包,fn实际上指向了addr1地址。

三、装饰器语法

装饰器是什么呢?他其实就是一种语法,怎么讲呢?我们直接看代码

def add(x,y):
    return x+y	

def logger(fn):
    def wrapper(*args,**kwargs)
    	print('调用前增强功能')
    	print('add函数它被调用了{},{}'.format(args, kwargs))
    	ret=fn(*args,**kwargs)  #这里就是闭包 
    	print('调用后增强功能')
    	ret1 = ret
    return wrapper

add = logger(add)
result=add(1,2)
print(result)

上面这段代码是我们分析过的,我们放在这里是为了对比的

@logger
def add(x,y):
    return x+y	

def logger(fn):
    def wrapper(*args,**kwargs)
    	print('调用前增强功能')
    	print('add函数它被调用了{},{}'.format(args, kwargs))
    	ret=fn(*args,**kwargs)  #这里就是闭包 
    	print('调用后增强功能')
    	ret1 = ret
    return wrapper

#add = logger(add) 这行代码不需要了注释掉
result=add(1,2)
print(result)

如果直接这样写,放到解释器中会报错的,因为@logger在add函数上面,但是这个
logger的定义是在add函数之后,所以才报错的我们需要进行调整。看下面的代码

在 add(x,y)函数上面加了一个 @logger,下面调用的add = logger(add)

这句代码就可以省略了

我们把logger函数的定义进行提前

def logger(fn):
    def wrapper(*args,**kwargs)
    	print('调用前增强功能')
    	print('add函数它被调用了{},{}'.format(args, kwargs))
    	ret=fn(*args,**kwargs)  
    	print('调用后增强功能')
    	ret1 = ret
    return wrapper

@logger   #add = logger(add)
def add(x,y):
    return x+y	
	
   #add = logger(add) 这行代码不需要了注释掉
result=add(1,2)
print(result)

上面这种写法就是装饰器语法。
加add函数上面加 @logger 就等价于add = logger(add)这句代码

这个就是所谓的用logger装饰了add函数

上面这种写法是无参装饰器的写法,@logger 后面是没有任何参数的。这样的装饰器就
是无参数装饰器

@标识符
	标识符号指向一个函数,用一个函数来装饰它下面的函数,logger函数
    称为装饰器函数,add函数称为是被包装(装饰)的函数。
		logger习惯上称为英文wrapper  包装
		add习惯上称为 wrapped 被包装
		本质上看 无参装饰器 logger实际上是等效为,一个参数的函数。
		称为无参装饰器,这个logger
			@logger 会把它下面紧挨着的函数的标识符号,提上来作为它的实参
				类似于这样add=logger(add)
						 add=wrapper

@logger就是装饰器语法了

四、应用

我们来写一个完善的装饰器功能,记录函数执行时间

#日志记录装饰器实现

import datetime
import time

def logger(fn):
    def wrapper(*args, **kwargs):
        print('调用前增强功能')
        start=datetime.datetime.now()
        ret = fn(*args, **kwargs)
        print('调用后增强功能')
        delta=(datetime.datetime.now()-start).total_seconds()
        print("Function {} took {:.2f}s".format(fn.__name__,delta))
        return ret
    return wrapper


@logger  # add = logger(add)
def add(x, y):
    time.sleep(2)
    return x + y

# add = logger(add) 这行代码不需要了注释掉
result=add(4, 5)
print(result)

每一个装饰器只解决一个功能











举报

相关推荐

0 条评论