0
点赞
收藏
分享

微信扫一扫

Python之带参装饰器(12)

一、文档字符串

无参装饰器和带参装饰器有什么区别呢?我们先来看文档字符串

文档字符串是什么东西呢?

文档字符串.
	●Python文档字符串Documentation Strings
	●在函数(类、模块)语句块的第一行,且习惯是多行的文本,所以多使用三引号
	●文档字符串也算是合法的一条语句
	●惯例是首字母大写,第一行写概述,空一行,第三行写详细描述
	●可以使用特殊属性__doc__ 访问这个文档

文档所在的对象上有一个特殊的属性叫做__name__,特殊属性就带下划线,还有特殊属性doc。自己也可以创建

它的用法如下

def add(x,y):
	"""add function
 		x int
 		y int
		return int 
  """   
  
  通过文档字符串说明了add函数的参数是什么类型,返回值是什么类型
  
  print(add.__name__,add.__doc__)	
  
  通过调用add属性__doc__可以看到三引号里面文档
  使用print(help(add)) 也可以输出文档

我们来写一个装饰器用来,记录函数执行时间的

import datetime
import time

def logger(fn):
    def wrapper(*args, **kwargs):        
        ''' wrapper function+++++++'''
        start=datetime.datetime.now()
        
        ret = fn(*args, **kwargs)      
        
        delta=(datetime.datetime.now()-start).total_seconds()
        print("Function {} took {:.2f}s".format(fn.__name__,delta))
        
        return ret
    return wrapper             #delta 输出多少秒


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


我们对里的装饰器加字符串文档

print(add.__name__,add.__doc__)
这里打印会发现输出了:
	wrapper  wrapper function+++++++
    发现并没有输出add函数中的字符串文档,有点露馅了,我们这种装饰,只是
    伪装的像add,名字是一样了。但是我们知道add = logger(add)
     add=wrapper add函数指向了wrpper函数的地址空间了。wrpper标识符虽然
    已经消亡了,但是它所指向的空间,被add标识符记住了。

现在问题是,我仅仅是标识符伪装了,都是add,但是内部还没有进行处理?该怎么处
	理就是你现在能不能把这个warpper伪装了像真正的add函数一样呢?
    名字和文档字符串都应该显示的是add函数的内容

简单的总结一下我们的问题是什么

print(add.__name__,add.__doc__)	

	输出了
		wrapper  wrapper function
		只是标识符用了add,文档字符串和名字依然用了wrapper,但是我需要
        输出add函数中的文挡字符串信息?该怎么办呢?
   要达到的效果是 输出 add add function~~~~~~

 怎么做有思路吗?

问题出在,因为我返回的是wrapper函数,wrapper所指向的函数对象呢,里面的函
	数名已经定义为wrapper了,里面的文档也写死了是
''' wrapper function ++++'''我们把doc文档换掉就好了,但是在哪里换呢?

	wrapper.__name__=fn.__name__
    wrapper.__doc__=fn.__doc__
    
    在wrapper函数中加这两行代码
    封装成函数 
    def copy_properties(dst,src):
        print(dst.__name__,'***',src.__doc__)
        dst.__name__=src.__name__
        dst.__doc__=src.__doc__
        #其他属性省略
        print(dst.__name__, '***', src.__doc__)

二、带参数装饰器

现在的问题是,这个用法比较麻烦能不能改造成装饰器?我们来看代码

def logger(fn):
    @copy_properties(fn)
    def wrapper(*args, **kwargs):
        ''' wrapper function+++++++'''
        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))
        									#delta 输出多少秒
        return ret

    copy_properties(wrapper,fn)
    return wrapper

现在加入了@copy_properties(fn),这个装饰器就是带参数装饰器。
因为这个装饰器参数,需要调用一个函数参数,所以是带参装饰器

我们在来看整体代码

import datetime
import time

#变成装饰器
def copy_properties(src):
    def copy_pro(dst):
        dst.__name__=src.__name__
        dst.__doc__=src.__doc__
    return copy_pro

def logger(fn):
    @copy_properties(fn)  #装饰了fn函数
    def wrapper(*args, **kwargs):
        ''' wrapper function+++++++'''
      
        start=datetime.datetime.now()
        ret = fn(*args, **kwargs)
        delta=(datetime.datetime.now()-start).total_seconds()
        print("Function {} took {:.2f}s".format(fn.__name__,delta))
        #delta 输出多少秒
        return ret
    #copy_properties(wrapper)(fn)#copy_pro(fn)

    return wrapper

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

# add = logger(add) 这行代码不需要了注释掉

print(add.__name__,add.__doc__)

上面代码执行会报错的我们来分析下错误原因:

这段代码的执行顺序如下:
	1.首先执行到@logger的时候,它其实是执行了add=logger(add)这个语句,
    因为@+函数标识符是装饰器语法,装饰器他会把它下面最近的函数标识符这里是
	add给抽取上来作为参数。

	因为add=logger(add)本质上是函数调用了,根据优先级原则,先做等式右边的
	logger(add)

	2.进入到logger 之后发现第一行是@copy_properties(fn)发现也是一个装饰
    器我们会发现这个@copy_properties(fn)装饰器后面是有参数的,这个参数是
     fn
      根据第一步我们知道,参数传入进来的其实是add函数。

	  在整个logger函数的执行,会把它下面定义的函数wrapper定义好,在进行
	  @copy_properties这个执行,它实质上也是一次函数调用,等价于
      wrapper=copy_propertties(fn)(wrapper),这是一个有参数的装饰器
	  修饰了wrapper.

    3.第三步我们需要进入到copy_propertties(src)这个函数内部进行执行,传
      进去参数是add函数,这个函数执行完会返回一个函数copy_pro(),我们在把
      参数wrapper给传入进去,这样一来dst和src都知道是谁了,目的是wrapper
	  源是src,add.
      但是copy_pro()函数内部只有两个语句,dst.__name__=src.__name__
      dst.__doc__=src.__doc__,我们又知道,函数内部不写返回值,默认就是
      返回NULL/相当于执行了一个return None。

      在根据装饰器语法@copy_properties(fn),执行这个相当于执行了
      wrapper=copy_propertties(fn)(wrapper),那我们就知道了,现在
      wrapper的值是NULL了,所以后面主函数我们在执行

           print(add.__name__,add.__doc__)会直接报错的

           add=logger(add)-->add=wrapper--->退出add=None
		
	我们怎么修改?让代码变的正确,我们写装饰器的时候,要明确给返回值。
		因为修改的是目的函数,所以我们就要返回dst这个参数函数就看可以了  
		  def copy_properties(src):
			    def copy_pro(dst):
			        dst.__name__=src.__name__
			        dst.__doc__=src.__doc__
			        return dst
                return  copy_pro

         	dst就是wrapper,修改一下返回。返回包装函数本身       

上面这种就是带参装饰器.




举报

相关推荐

0 条评论