• 谈谈Python中的decorator装饰器,如何更优雅的重用代码


    众所周知,Python本身有很多优雅的语法,让你能用一行代码写出其他语言很多行代码才能做的事情,比如:

    最常用的迭代(eg: for i in range(1,10)), 列表生成式(eg: [ x*x for x in range(1,10) if x % 2 ==  0])

    map()能让你把函数作用于多个元素, reduce()能让你把多个元素的结果按照你预想的方式组合在一起,filter()能让你快速筛选出复合条件的数据

    以上具体用法可以参考https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014317793224211f408912d9c04f2eac4d2af0d5d3d7b2000

    而我们这次要讨论的装饰器decorator,可以在不改变现有函数的前提下更有效率的重用代码

    比如我们在实际工作当中,经常需要添加try...except来捕获异常,但是一个个加也太麻烦了,此时我们就可以用decorator装饰器来实现

    比如我们有以下原始函数

    def hello():
        print("Hello, world!")
    
    def bye():
        print("Bye, world!")

    正常情况下,如果都需要捕获异常的话,需要加两次try...except来做:

    def main():
        try:
            hello()
        except Exception as e:
            print('except:', e)
        ....
        ....
        try:
            bye()
        except Exception as e:
            print('except:', e)
        ....
        ....

    但是当我们有装饰器decorator的时候,一切都会变得特别优雅而简单,首先定义好我们的装饰器:

    import functools
    
    def decorator_try(func):
        @functools.wraps(func)
        def wrapper(*arg,**kw):
            try:
                func(*arg, **kw)
            except Exception as e:
                print('except:', e)
        return wrapper

    然后只需要在原来的hello()和bye()函数定义之前添加一行语法就可以:

    @decorator_try
    def hello():
        print("Hello, world!")
    
    @decorator_try
    def bye():
        print("Bye, world!")

    然后执行的时候任何东西都不用加

    def main():
        hello()
        ....
        ....
        bye()

    结果为:

    >>> hello()
    Hello, world!
    >>> hello(1,2)
    except: hello() takes 0 positional arguments but 2 were given

    具体的关于decorator装饰器的语法解释可以参考https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014318435599930270c0381a3b44db991cd6d858064ac0000

    另外要注意装饰器定义中 func(*arg, **kw) 和 return wrapper的区别,注意看一个是带参数,一个不带参数与括号,带参数表示执行这个函数,不带参数和括号代表把定义的函数作为一个参数传递了过去,这对理解decorator的语法是至关重要的。因为:

    @decorator_try放到hello()函数的定义前,相当于执行了语句:

    hello = decorator_try(hello)

     如果想更深入的了解decorator装饰器,推荐一篇博文https://www.cnblogs.com/zh605929205/p/7704902.html

    下面再写一个例子:

    比如我们有一个函数,下载图片,用装饰器实现timeout之后,自动重新下载一次。

    import functools
    import random
    
    # 定义当timeout发生时要抛出的异常
    class TimeOutError(Exception):
        pass
    
    # 定义装饰器
    def retry(func):
        @functools.wrap(func)
        def wrapper(*arg,**kwarg):
            try:
                print("first try...")
                func(*arg,**kwarg)
            except TimeOutError:
                print("timeout occurs, retrying...")
                func(*arg,**kwarg)
      return wrapper  
    # 定义download函数
    @retry def download(): print("downloading the photos...") download_time = random.ranint(1,2) if download_time>1: print("the download_time > 1s, time out") raise TimeOutError else: print("download finished.")

    如果我们想要带参数的装饰器,则需要再多加一层函数嵌套:

    #!/usr/bin/python
    #-*- coding:utf-8 -*-
    
    import functools
    import random
    
    # 定义当timeout发生时要抛出的异常
    class TimeOutError(Exception):
        pass
    
    # 定义装饰器
    def decorator_download(text):
        def decorator(func):
            @functools.wraps(func)
            def wrapper(*arg,**kwarg):
                #try:
                #    print("first try...")
                #    func(*arg,**kwarg)
                #except TimeOutError:
                #    print("timeout occurs, retrying...")
                #    func(*arg,**kwarg)
                print(text)
                print("first try...")
                result = func(*arg,**kwarg)
                while result == False:
                    print("will retry...")
                    result = func(*arg,**kwarg)
            return wrapper
        return decorator
    
    # 定义download函数
    @decorator_download("retry until download finished successfully")
    def download():
        print("downloading the photos...")
        download_time = random.randint(1,2)
        if download_time>1:
            print("the download_time > 1s, time out")
            #raise TimeOutError
            return False
        else:
            print("download finished.")
            return True
            
    if __name__ == "__main__":
        download()
  • 相关阅读:
    Linux异常现场--pt_regs浅析
    内核调试--确认结构体的size和结构体成员的偏移
    Linux内核中root_domain的建立与初始化
    solr学习笔记-全量更新与增量更新,去除html标签
    oracle 替换clob里面的某个特定的字符串
    oracle重置序列从1开始
    oracle提取汉字拼音首字母
    layer.open的yes函数中获取弹出层(子集iframe)中的元素或参数
    java.util.Date日期时间工具类
    js操作将数字转换成 , 逗号分割的字符串并追加‘万’字
  • 原文地址:https://www.cnblogs.com/ArsenalfanInECNU/p/8657359.html
Copyright © 2020-2023  润新知