深度思维者

永远年轻,永远热泪盈眶

python装饰器进阶

装饰器进阶

通过上一篇已经知道, 如果还有不理解什么是装饰器,请返回 装饰器入门。 前面一篇算是装饰器的入门 。装饰器在 python语言中有着非常重要的应用。小到脚本的开发,大到很多功能负责的web框架开发。正如我们所知, 前面所看到的被装饰的 函数都是最简单函数格式, 这里所说的简单, 是指 函数无参数的形式,或者函数没有返回(直接执行结果)值。 在实际开发中 被装饰的函数并非我们见到如此简单的格式,更多的复杂的函数如多返回值多参数(形式参数,关键字参数, 所以这一篇算是python装饰器的一个进阶。

1. 被装饰的函数有多个参数。

场景一: 一个简单的认证装饰器

  • 实例代码

    from functools import wraps
    
    def auth2(func):
        @wraps(func)
        def wrapper(*arg, **kwargs):
            user = input("pls input password:").strip()
            if user == "faily":
                print("---welcome login----")
                func(*arg, **kwargs)
    		else:
    			print("----wrong password----")
    	return wrapper
    
    @auth2
    def task2(name, is_happy="Yes"):
                    print("{0} is doing somthing, Is he happy? [{1}]".format(name, is_happy))
            
    
    if __name__ =="__main__":
         '''带参数的装饰器'''
         task2("faily")
    

    可以看出,被装饰的函数task2 拥有两个参数,name, is_happy, 那么如何将两个参数传递进装饰器函数中呢?

  1. 根据前面我们所学的知识,可以看出, 装饰器在执行的时候, 首先将被装饰的 函数名 这里是task2传递给装饰器函数 auth2, 那么被装饰函数的参数 就不得不找别的地方传进去, 唯一可以接受这些变量的地方就是 wrapper函数.
  2. 通过wrapper 函数将参数传进去之后呢, 再建参数传递个 被装饰的函数 即可

2. 被装饰的函数有返回值

  • 显然 这种函数最普遍, 函数在执行后立即展示结果,而是作为值进行传递

    from functools import wraps
    
    def auth3(func):
    
        @wraps(func)
        def wrapper(arg):
            user = input("pls input password:").strip()
            if user == "faily":
                print("---welcome login----")
                return func(arg)
            else:
                print("----wrong password---")
    
        return wrapper
    
    @auth3
    def task3(name):
        print("do somthing ")
        return name
    
    if __name__ == "__main__":
        ts = task3("momo")
        print(ts)
    

    这种很好理解, 函数有返回值, 那么在wrapper函数内执行完新加功能后, 直使用关键字 return将被装饰的函数 返回

3.在函数中嵌入装饰器

在这里我们可以这样理解: 既然装饰器是高阶函数 + 嵌套函数 = 装饰器 那么可不可以有这种组合呢, 即 嵌套函数 + 装饰器函数的形式呢?

答案是肯定的

其实这种应用也可理解为: 之前我们写的所有装饰器函数自己都不带参数的,既然装饰器本质也是函数 ,那么装饰器函数是否可以带参数呢?? 格式是什么样子呢?
通过下面的例子进行说明

  • 回到日志的例子,并创建一个包裹函数,能让我们指定一个用于输出的日志文件

    from functools import wraps
    
    def logit(logfile='out.log'):
        
        # 装饰器 
        def logging_decorator(func):
            @wraps(func)
            def wrapped_function(*args, **kwargs):
                log_string = func.__name__ + " was called"
                print(log_string)
                # 打开logfile,并写入内容
                with open(logfile, 'a') as f:
                    # 现在将日志打到指定的logfile
                    f.write(log_string + '\n')
                return func(*args, **kwargs)
           return wrapped_function
        return logging_decorator
    
    #默认装饰器参数
    @logit()
    def myfunc1(action="run"):
        if action == "run":
            return True
        else:
            return False
    
    #装饰器传参
    @logit(logfile='func2.log')
    def myfunc2(action = "stop"):
        if action == "run":
            return True
        else:
            return False
        
    if __name__ == "__main__":
    	myfunc1()
    	myfunc2()
        
    # 现在一个叫做 func2.log 的文件出现了,里面的内容就是上面的字符串 
    

    这个例子中, 是不是觉得装饰器实在是太强大了?

  • 可以通过为装饰器给予不同的参数实现不同函数日志输出的新增功能, 我们只是在 标准装饰器外加了一层函数, 这个函数实际就是为了用来将装饰器的参数传递进包裹函数中.

4. 装饰器类

场景分析 : 在运维监控中, 我们常常需要记录不同级别,或者不同app产生的日志,但当我们的应用程序的某些部分还比较脆弱时,触发异常也许是需要更加关注的事情. 比方说有时只想记录日志到一个文件; 而有时你想在程序发生异常时送到一个email,同时也保留日志。这是一个使用继承的场景,但目前为止我们只看到过用来构建装饰器的函数。

幸运的是,类也可以用来构建装饰器。那我们现在以一个类而不是一个函数的方式,来重新构建logit

  • 装饰器类

    rom functools import wraps
    
    class logit(object):
        '''装饰器类'''
        def __init__(self, logfile='out.log'):
            self.logfile = logfile
    
        def __call__(self, func): # __call__说明这是一个callable
            @wraps(func)
            def wrapped_function(*args, **kwargs):
                log_string = func.__name__ + " was called"
                print(log_string)
                # 打开logfile并写入
                with open(self.logfile, 'a') as f:
                    # 现在将日志打到指定的文件
                    f.write(log_string + '\n')
                # 现在,发送一个通知
                self.notify()
                return func(*args, **kwargs)
            return wrapped_function
    
        def notify(self):
            # logit只打日志,不做别的
            pass
    

    这个实现有一个附加优势,在于比嵌套函数的方式更加整洁,而且包裹一个函数还是使用跟以前一样的语法:

    @logit()
    def myfunc1():
        pass
    

    现在,我们给logit创建子类,来添加email的功能。

    class email_logit(logit):
        '''
        一个logit的实现版本,可以在函数调用时发送email给管理员
        '''
        def __init__(self, email='admin@myproject.com', *args, **kwargs):
            self.email = email
            super(email_logit, self).__init__(*args, **kwargs) # 集成父类
    
        def notify(self):
            # 发送一封email到self.email
            # 这里就不做实现了
            pass
    

    从现在起,@email_logit将会和@logit产生同样的效果,但是在输出日志的基础上,还会多发送一封邮件给管理员。

总结

装饰器进阶已经讲完, 下一篇我们继续研究, 多重装饰器

posted @ 2019-03-03 15:57  failymao  阅读(481)  评论(0编辑  收藏  举报