python裝飾器入門

python裝飾器

函數也是對象

在python中,function是一級對象,這也就意味着function可以作爲arguments一樣傳遞,就像其它的對象一樣(字符串,整型變量,浮點型變量,列表等等)。當函數名像其它類型的變量一樣作爲另一個函數的參數被傳遞時,實現了函數的嵌套調用。

def say_hello(name):
    return f"Hello {name}"

def be_awesome(name):
    return f"Yo {name}, you are awesome!"

def greet_bob(greeter_func):
    return greeter_func("Bob")

print(greet_bob(say_hello))
print(greet_bob(be_awesome))
Hello Bob
Yo Bob, you are awesome!

函數內部定義的函數成爲inner function, 內部函數。內部函數只存在在局部空間,也就是說只能在定義的函數中被調用,下面的例子就說明了這一點。

def parent():
    print('The parent() function')
    
    def first_child():
        print('The first_child() function')
    
    def second_child():
        print('The second_child() function')
        
    second_child()
    first_child()

parent()
The parent() function
The second_child() function
The first_child() function

函數名也可以用作返回參數。這時,內部定義的函數也可以被外部訪問到,可以當作一個普通函數調用,也就是要加上()。

def parent(num):
    def first_child():
        return "HI I am Emma"
    def second_child():
        return "HI I am Lin"
    if num == 1:
        return first_child
    else: 
        return second_child

first_child = parent(1)
second_child = parent(2)

print(first_child()) # 注意不是print(first_child)
print(second_child()) 

HI I am Emma
HI I am Lin
print(first_child)
<function parent.<locals>.first_child at 0x1115e74d0>

簡單裝飾器

理解了函數是python對象,函數名可以作爲參數傳來傳去這個點後,我們下面來個稍微有點難度的例子,let’s go!

def my_decorator(func):
    def wrapper():
        print("Before the function is called")
        func()
        print("After the function is called")
        
    return wrapper

def say_whee():
    print("whee")
    
say_whee = my_decorator(say_whee)


say_whee()
Before the function is called
whee
After the function is called

say_whee = my_decorator(say_whee)這一句就是應用了所謂的函數裝飾器。事實上,這裏把say_whee函數名作爲my_decorator的輸入參數,之後將返回的函數名wrapper賦予了最左側的say_whee變量。這時候,最左側的say_whee實際上指向了my_decorator的內部函數wrapper()。

簡單滴說,就是“裝飾器“是封裝了一系列函數的行爲,將被裝飾的函數嵌入到這些行爲中。(decorators wrap a function, modifying its behavior)”
下面的例子中,函數被限制在了工作時間執行(7到22點),首先看看如果用普通的函數定義:

from datetime import datetime

def not_during_the_night(func):
    def wrapper():
        if 7 <= datetime.now().hour < 22:
            func()
        else:
            pass
    return wrapper

def say_whee():
    print("Whee!")

say_whee = not_during_the_night(say_whee)
say_whee()  
Whee!

裝飾器@ 語法給上面的例子提供了更加簡介的解決方案。值得注意的是,裝飾器構造函數一定要返回行爲定義內部函數的函數名,裝飾器使用的時候是在新函數定義的前一行,用@符號後面跟着裝飾器函數名,不加()。例如:

@not_during_the_night
def say_whee_in_work_hours():
    print("Whee")
    
say_whee_in_work_hours()
Whee
def do_twice(func):
    def wrapper_do_twice():
        func()
        func()
    return wrapper_do_twice

@do_twice
def say_whee():
    print("Whee")

say_whee() 
Whee
Whee

被修飾函數有參數的情況

上面的例子中,被修飾的函數都是沒有輸入和輸出參數的。我們先來看看下面的這個例子:

@do_twice
def greet(name):
    print(f"Hello {name}")

greet("Alice")

報錯:greet() missing 1 required positional argument: ‘name’
這是什麼情況?回憶一下裝飾器的工作原理,在函數執行的時候,實際上是傳給了do_twice裝飾器函數,在這個函數內部再執行greet這個函數,但是這時greet函數沒有輸入參數。怎麼解決問題?這時應該用到python的可變參數。

def do_twice_with_args(func):
    def wrapper_do_twice(*args,**kwargs):
        func(*args,**kwargs)
        func(*args,**kwargs)
        #return wrapper_do_twice #報錯:'NoneType' object is not callable
    return wrapper_do_twice

@do_twice_with_args
def greet(name):
    print(f"Hello {name}")
    
greet("Alice")
Hello Alice
Hello Alice

如果函數有返回值呢?

@do_twice_with_args
def greet_back(name):
    return f"Hello {name}"
print(greet_back("Ben"))
None

返回的是None。顯然裝飾函數運行過程中“吞”掉了greet_back函數的輸出,就在裝飾器內部函數wrapper_do_twice()內部,調用傳入的函數時,沒有返回值。我們修改一下do_twice_with_args的定義,在wrapper_to_twice函數後面添加一個return語句。
事實上,之前返回的None是python函數在用戶沒有定義return語句時默認返回的值。

def do_twice_with_args(func):    
    def wrapper_do_twice(*args, **kwargs):  
        func(*args, **kwargs)
        return func(*args, **kwargs)        
    return wrapper_do_twice

@do_twice_complex
def greet_back(name):
    print('Hello, Alice')
    return f"Hello, {name}"

def no_return():
    print()
    print('default return should be ')
    

hi = greet_back("Ben")
print(hi)

ret = no_return()
print(ret)
Hello, Alice
Hello, Alice
Hello, Ben

default return should be 
None

通常我們用__定義對象的內部變量和內部函數,python的這一機制便於查看對象的基本情況,還可以用內置的help函數查看對應的幫助文檔。例如:

print("print.__name__:   "print.__name__)
print(help(print))
print
Help on built-in function print in module builtins:

print(...)
    print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
    
    Prints the values to a stream, or to sys.stdout by default.
    Optional keyword arguments:
    file:  a file-like object (stream); defaults to the current sys.stdout.
    sep:   string inserted between values, default a space.
    end:   string appended after the last value, default a newline.
    flush: whether to forcibly flush the stream.

None

但是在裝飾器語法裝飾後,會讓被裝飾函數喪失這個特性,有的時候會給代碼編寫和測試帶來麻煩,如何修復這個問題?
可以用functools工具包解決。例如:

print('greet_back.__name__:  '+greet_back.__name__)
print(help(greet_back))
print()
print("import functools toolbox to fix this. ")

import functools

def do_twice(func):
    @functools.wraps(func)
    def wrapper_do_twice(*args, **kwargs):
        func(*args, **kwargs)
        return func(*args, **kwargs)
    return wrapper_do_twice

@do_twice
def greet(name):
    print('Hi! ')
    return f"Hello, {name}"

print(greet("Ti-Ya"))
print('greet.__name__:  '+greet.__name__)

greet_back.__name__:  wrapper_do_twice
Help on function wrapper_do_twice in module __main__:

wrapper_do_twice(*args, **kwargs)

None

import functools toolbox to fix this. 
Hi! 
Hi! 
Hello, Ti-Ya
greet.__name__:  greet

到這裏,python裝飾器的定義和基本使用就講完了。下面給出實際中的例子。

裝飾器的應用

裝飾器函數代碼的一般結構

首先來總結下一個裝飾器函數的一般寫法:

import functools

def decorator(func):
    @functools.wraps(func)
    def wrapper_decorator(*args, **kwargs):
        # Do something before
        value = func(*args, **kwargs)
        # Do something after
        return value
    return wrapper_decorator

記時裝飾器

記錄函數運行時間

## 記時裝飾器
import functools
import time

def timer(func):
    """Print the run time of the decorated function."""
    @functools.wraps(func)
    def wrapper_timer(*args, **kwargs):
        start_time = time.perf_counter()
        value = func(*args, **kwargs)
        end_time = time.perf_counter()
        run_time = end_time - start_time
        print(f"Fininshed {func.__name__} in {run_time:.4f} secs")
        return value
    return wrapper_timer

import tensorflow as tf
import numpy as np
@timer
def tensor_computation():
    a = tf.constant(np.random.randn(3,3))
    b = tf.constant(np.random.randn(3,3))
    c = tf.matmul(a,b)
    return c

tensor_computation()
Fininshed tensor_computation in 0.0002 secs





<tf.Tensor: shape=(3, 3), dtype=float64, numpy=
array([[-0.67434088,  1.34905892,  0.59164407],
       [-2.14192767,  1.13678956, -0.48185652],
       [-0.6749793 ,  1.35129415, -1.46809712]])>

debug模式裝飾器

可以定義debug裝飾器時對函數的執行情況進行檢查,這裏給個簡單的例子。repr()是python的內置函數,和str()用法類似但又有相同。詳見:https://www.geeksforgeeks.org/str-vs-repr-in-python/

此外,f""字符串中的!r代表按照兩邊帶引號的字符串進行打印。關於f-string更多的說明和幫助在這裏:
https://www.python.org/dev/peps/pep-0498/#s-r-and-a-are-redundant

import functools

def debug(func):
    """Print the function signature and return value"""
    def wrapper_debug(*args,**kwargs):
        args_repr = [repr(a) for a in args]
        kwargs_repr = [f"{k}={v!r}" for k,v in kwargs.items()]
        signature = ','.join(args_repr+kwargs_repr)
        print(f"Calling {func.__name__}({signature})")
        value = func(*args, **kwargs)
        print(f"{func.__name__!r} returned {value!r}")
        return value
    return wrapper_debug

@debug
def make_greeting(name, age=None):
    if age is None:
        return f"Howdy {name}!"
    else:
        return f"Whoa {name}! {age} already, you are growing up!"
    
make_greeting("Benjamin")
make_greeting("Richard", age=112)

print()
rname = "Neptune"
print(f"{rname!r} is a cute boy!")
Calling make_greeting('Benjamin')
'make_greeting' returned 'Howdy Benjamin!'
Calling make_greeting('Richard',age=112)
'make_greeting' returned 'Whoa Richard! 112 already, you are growing up!'

'Neptune' is a cute boy!
發佈了115 篇原創文章 · 獲贊 123 · 訪問量 28萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章