python 裝飾器

在 python 中,咱們常常能夠看到這樣的函數定義:python

@staticmethod
def func():
    ... ...

不去看第一句,剩下的就是一個普通的函數定義了,那麼加上第一句app

@staticmethod

是什麼意思?又有什麼用處呢?函數

咱們從字面上能夠猜想,這裏定義的 func 函數是靜態函數,至因而什麼是靜態函數,在本文中就不作介紹了,有意思能夠閱讀這篇文章瞭解。那加上這個以後有什麼用,python 對咱們定義的 func 函數作了什麼操做呢?spa

若是你看過一些關於 staticmethod 的資料的話,你也許會知道最開始定義 staticmethod 的方式是:調試

func = staticmethod(func)

也就是說,加入我要在類 Foo 中定義一個靜態函數,那麼我須要這麼寫:code

class Foo(object):
    def func():
        ... ...
    func = staticmethod(func)

Foo.func()

看一下這種方式,咱們發現明顯很累贅,因此裝飾器就來解救咱們於繁瑣之中,咱們只須要加一個裝飾器,就將函數定義簡化成了:orm

class Foo(object):
    @staticmethod
    def func():
        ... ...

Foo.func()

因此,從這裏咱們就知道了裝飾器就是簡化函數調用的一種方式get

@wrap
def func():
    ... ...
func()

等價於:it

wrap(func)()

舉個栗子

說了一些概念以後,咱們是時候上個例子了,這個例子我想實現的功能就是可以計算函數執行的時間,例如xx 函數執行花費了 3S 之類的,代碼以下:io

def my_decorator(func):
    def wrapper_fun():
        begin_time = time()
        func()
        end_time = time()
        print int(end_time - begin_time)
    return wrapper_fun

@my_decorator
def test():
    print "test func run"
    sleep(3)

test()

這個例子很簡單,就是計算一下 test 函數執行了多久,咱們解析開來看其實調用就是這樣的:

my_decorator(test)()

而後就會打印出花費的時間。

帶參數的函數

第一個例子太簡單了,以致於咱們都沒感受到有趣,因此此次咱們加上個參數試試:

def my_decorator(func):
    def wrapper_fun():
        begin_time = time()
        func()
        end_time = time()
        print int(end_time - begin_time)
    return wrapper_fun

@my_decorator
def test(arg):
    print "test func run with arg: {}".format(arg)

test('hello')

而後跑一遍,發現果斷出錯了。咱們分析一下問題,這裏的調用解開來其實就是:

my_decorator(test)('hello')

my_decorator(test)

返回的是 wrapper_fun(),沒有帶參數,因此代碼執行失敗很正常。既然知道了緣由,那麼修改起來也方便了,就是給 wrapper_fun 加上參數:

def my_decorator(func):
    def wrapper_fun(*args, **kwargs):
        print "decorator begin"
        func(*args, **kwargs)
        print "decorator end"
    return wrapper_fun

@my_decorator
def test(arg):
    print "test func run with arg: {}".format(arg)

test('hello')

而後跑一遍代碼,運行起來了,而且打印出:

decorator begin
test func run with arg: hello
decorator end

裝飾器帶參數

終於有點意思了,如今咱們能夠調用帶參數的函數了,可是,喜歡瞎折騰的我又有了新的想法,我好想見過裝飾器帶參數的,那這樣怎麼實現呢?根據套路,咱們仍是以解開的函數調用來進行思考:

若是裝飾器帶參數,那麼,咱們是否是能夠這樣子:

my_decorator(test, 'decorator_arg')('hello')

這樣作的話,那麼咱們的裝飾器要怎麼寫呢?這樣子?:

def my_decorator(func, *oargs, **okwargs):
        def wrapper_fun(*iargs, **ikwargs):
            print "decorator begin"
            func(*iargs, **ikwargs)
            print "decorator end"
        return wrapper_fun

那明顯不行啊,由於裝飾器是 Python 自帶的語法糖,咱們不能控制它的參數個數,就只能是一個參數,並且就是咱們被封裝的函數,否則就不是裝飾器了。那說明咱們這個方法不行,那就換個思路:

my_decorator('decorator_arg')(test)('hello')

這樣子行不行,這樣子的話,咱們既給裝飾器帶了參數,又給函數帶了參數,那麼轉換成裝飾器能不能實現呢?試一下:

def my_decorator(name):
    def decorator_func(func):
        def wrapper_fun(*args, **kwargs):
            func(*args, **kwargs)
        return wrapper_fun
    return decorator_func

看下這個能不能實現咱們的想法,首先

  • my_decorator('decorator_arg') =====> decorator_func
  • my_decorator('decorator_arg')(test) =====> decorator_func(test) =====> wrapper_fun
  • my_decorator('decorator_arg')(test)('hello') =====> wrapper_fun('hello') =====> func('hello')

是的,這樣一解析以後就發現是實現了咱們的想法。

改變狀態

目前爲止咱們就已經實現了帶參數函數的裝飾器,裝飾器帶參數的功能,接下來還有什麼問題呢?好像暫時想不到,那咱們就寫個例子看看:

def func():
    """function for test"""
    print "functiong calling"
print func.__name__
print func.__doc__

而後執行一遍看下結果:

func
function for test

沒什麼問題,接着來看下這個:

def my_decorator():
    def decorator_func(func):
        def wrapper_fun(*args, **kwargs):
            func(*args, **kwargs)
        return wrapper_fun
    return decorator_func

@my_decorator
def func():
    """function for test"""
    print "functiong calling"

print func.__name__
print func.__doc__

而後運行一遍,看看輸出:

decorator_func
None

WTF,怎麼了,什麼狀況,個人名字呢?很明顯,如今的 func 已經被隱藏了,取而代之的是 decorator_func,那麼,加入咱們調試代碼或者出 bug 了要怎麼定位到真正的出問題的 func 呢?那仍是須要把 name 和 doc 這些成員變量找回來的。這裏給個坑本身,先不詳解:

from functools import wraps

def my_decorator():
    def decorator_func(func):
        @wraps(func)
        def wrapper_fun(*args, **kwargs):
            func(*args, **kwargs)
        return wrapper_fun
    return decorator_func

@my_decorator()
def func():
    """function for test"""
    print "functiong calling"

print func.__name__
print func.__doc__
相關文章
相關標籤/搜索