1. 程式人生 > 實用技巧 >Python的裝飾器理解

Python的裝飾器理解

python的裝飾器其實是一個語法糖,給出一個python裝飾器的程式碼:

import time

def timer(func):

    def warp(*args):
        t1 = time.time()
        res = func(*args)
        t2 = time.time()
        delta = t2 - t1
        return res, delta * 1000

    return warp

很顯然,這是一個計算測試函式執行時間的函式,假如我們想測試一個函式的執行時間,我們只需要在待測函式的上方增加@timer即可,程式碼如下:

@timer
def test1():
    return 10 ** 2

在main函式中我們只需要按照如下方式呼叫即可:

if __name__ == "__main__":    
    res, delta = test1()
    print(res, delta)  # 100, 0.0

如果不使用這個語法糖,我們可以按照如下方式呼叫:

def test2():
    return 10 ** 2

if __name__ == "__main__":

    tmp_fun = timer(test2)
    res, delta = tmp_fun()
    print
(res, delta) # 100, 0.0

將上述程式碼整合的結果就是:

import time

def timer(func):

    def warp(*args):
        t1 = time.time()
        res = func(*args)
        t2 = time.time()
        delta = t2 - t1
        return res, delta * 1000
    return warp

@timer
def test1():
    return 10 ** 2
    
def test2():
    return
10 ** 2 if __name__ == "__main__": tmp_fun = timer(test2) res, delta = tmp_fun() print(res, delta) res, delta = test1() print(res, delta)

但是這裡還可能存在這一些問題,比如當我們輸出test1.__name__時我們會得到裝飾器的名字這顯然不是我們想要的:

print(test1.__name__)  # warp, 我們希望可以得到test1而非timer
print(test2.__name__)  # test2

很容易想到一個簡單的解決方法:

def timer(func):
    
    @functools.wraps(func)
    def warp(*args):
        t1 = time.time()
        res = func(*args)
        t2 = time.time()
        delta = t2 - t1
        return res, delta * 1000
    warp.__name__ = func.__name__  # 增加這一句程式碼
    return warp

但這並不推薦,python為我們專門提供了相關的工具,我們只需要使用即可:

import functools

def timer(func):
    
    @functools.wraps(func)
    def warp(*args):
        t1 = time.time()
        res = func(*args)
        t2 = time.time()
        delta = t2 - t1
        return res, delta * 1000
    # warp.__name__ = func.__name__
    return warp

這樣我們就可以得到我們想要的名字了。另外如果你想在c++裡面實現類似的功能也是可以的,這裡提供一份c++17的程式碼,由於我們可能需要使用decltype(auto)所有至少需要c++14的標準才可以,相信你可以將這份程式碼使用c++14進行重寫。由於這邊文章主要是說python的裝飾器,故對c++程式碼不做過多解釋:

#include <chrono>
#include <iostream>
#include <tuple>

template <typename Fn>
decltype(auto) timer(Fn func)
{
    return [&](auto&&... ts) -> decltype(auto)
    {
        auto t1 = std::chrono::high_resolution_clock::now();
        std::chrono::duration<double> duration;
        if constexpr (std::is_same_v<std::invoke_result_t<Fn, decltype(ts)...>, void>)
        {
            func(std::forward<decltype(ts)>(ts)...);
            auto t2 = std::chrono::high_resolution_clock::now();
            duration = t2 - t1;
            return duration;
        } 
        else
        {
            auto res = func(std::forward<decltype(ts)>(ts)...);
            auto t2 = std::chrono::high_resolution_clock::now();
            duration = t2 - t1;
            return std::make_pair(res, duration);
        }
    };
}

template <typename Fn>
decltype(auto) make_timer_decorate(Fn&& fn)
{
    return timer(std::forward<Fn>(fn));
}

uint64_t get_sum(int n)
{
    uint64_t res = 0;
    for (int i = 1; i <= n; ++i)
    {
        res += i;
    }
    return res;
}

void test1()
{
    auto d = make_timer_decorate(get_sum);
    auto [res, t] = d(10000005);
    std::cout << res << std::endl;
    std::cout << t.count() * 1000 << "ms" << std::endl;
}

void print(int& x)
{
    x++;
    std::cout << x << std::endl;
}
void test2()
{
    auto d = make_timer_decorate(print);
    int x = 10;
    auto res = d(x);
    std::cout << res.count() * 1000 << "ms" << std::endl;
}

int main()
{
    test1();
    test2();
}