初窥Python(五)——python中

发布时间:2019-09-05 07:03:52编辑:auto阅读(1305)



    1. 介绍

    decorator是用来在代码运行期间动态增加功能的,本质上是一个返回函数的高阶函数。假设现在有这样一种需求,即在每个函数调用前记录日志,记录被调用的函数名称,可以这样实现:

    def log(func):
        def wrapper(*args, **kwargs):
            print "CALL %s()" % func.__name__
            return func(*args, **kwargs)
        return wrapper
    
    def sayHi():
        print "Hi, Buddy."
    
    def sayHello():
        print "Hello, Buddy."
    
    # 调用函数,记录日志
    log(sayHi)()
    # 输出为
    # CALL sayHi()
    # Hi, Buddy.
    log(sayHello)()
    # 输出为
    # CALL sayHello()
    # Hello, Buddy.

    这种方法确实实现了记录日志的功能,但每次这么调用未免太过繁琐,decorator因此出现。

    2. 使用

    其实,之前定义的log函数即为一个decorator,只是使用方式不正确:

    @log
    def sayHi():
        print "Hi, Buddy."
    
    sayHi()
    # 输出为
    # CALL sayHi()
    # Hi, Buddy.
    
    @log
    def sayHello():
        print "Hello, Buddy."
    
    sayHello()
    # 输出为
    # CALL sayHello()
    # Hello, Buddy.

    可以看到,使用decorator非常简单方便,def sayHi():前的@log相当于将sayHi作为参数传入log函数中,并将返回值赋给sayHi,即:

    sayHi = log(sayHi)

    但是细心的读者不难发现,这样一来函数sayHi__name__属性发生变化,由之前的sayHi变为wrapper,使用python内置的functools.wraps方法可以解决这一问题,改造后的decorator如下:

    import functools 
    
    def log(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print "CALL %s()" % func.__name__
            return func(*args, **kwargs)
        return wrapper

    让我们更进一步,使用三层嵌套的decorator,允许再多传入一次参数:

    import functools
    
    def log(text="CALL")
        def decorator(func):
            @functools.wraps(func)
            def wrapper(*args, **kwargs):
                print "%s %s()" % (text, func.__name__)
                return func(*args, **kwargs)
            return wrapper
        return decorator

    这次我们可以选择传入一个表示函数运行状态的字符串,由于多了一层嵌套,使用时也会有些变化:

    @log("EXECUTE")
    def sayHi():
        print "Hi, Buddy."
    
    sayHi()
    # 输出为
    # EXECUTE sayHi()
    # Hi, Buddy.

    修改过后嵌套使用为:

    sayHi = log("EXECUTE")(sayHi)

    3. 拓展

    3.1

    修改log函数,使该decorator既可以通过

    @log

    使用,又可以通过

    @log("EXECUTE")

    使用:

    import functools
    
    def log(text="CALL"):
        if callable(text):
            func = text
            @functools.wraps(func)
            def wrapper(*args, **kwargs):
                print "CALL %s()" % func.__name__
                return func(*args, **kwargs)
        else:
            def decorator(func):
                @functools.wraps(func)
                def wrapper(*args, **kwargs):
                    print "%s %s()" % (text, func.__name__)
                    return func(*args, **kwargs)
                return wrapper
            return decorator

    两种使用方式:

    @log
    def sayHi():
        print "Hi, Buddy."
    
    sayHi()
    # 输出为
    # CALL sayHi()
    # Hi, Buddy.
    
    @log("EXECUTE")
    def sayHello():
        print "Hello, Buddy."
    
    sayHello()
    # 输出为
    # EXECUTE sayHello()
    # Hello, Buddy.
    3.2

    修改log函数,使该decorator在函数调用前及函数调用后分别输出一条日志:

    import functools
    
    def log(func):
        def wrapper(*args, **kwargs):
            print "CALL BEGINNING"
            call = func(*args, **kwargs)
            print "CALL ENDING"
            return call
        return wrapper

    使用该decorator

    @log
    def sayHi():
        print "Hi, Buddy."
    
    sayHi()
    # 输出为
    # CALL BEGINING
    # Hi, Buddy.
    # CALL ENDING

    参考资料:

    廖雪峰的官方网站

关键字