python裝飾器

部分細節自己改了點,也加了點自己例子,基本上屬于轉載。
轉載出處:https://my.oschina.net/leejun2005/blog/477614

Python的裝飾器的英文名叫Decorator,基本上適用的場景就是“裝修”:不涉及主流程業務,用于鑒權、審計等副業。

1.函數

在python中,函數通過def關鍵字、函數名和可選的參數列表定義。通過return關鍵字返回值。我們舉例來說明如何定義和調用一個簡單的函數:

def foo():
    return 1
foo()
1

方法體(當然多行也是一樣的)是必須的,通過縮進來表示,在方法名的后面加上雙括號()就能夠調用函數(括號里根據情況放入實參)

2.作用域

在python中,函數會創建一個新的作用域。python開發者可能會說函數有自己的命名空間,差不多一個意思。這意味著在函數內部碰到一個變量的時候函數會優先在自己的命名空間里面去尋找。讓我們寫一個簡單的函數看一下 本地作用域全局作用域有什么不同:

a_string = "This is a global variable"
def foo():
    print locals()

print globals()
{..., 'a_string': 'This is a global variable'}

foo() # 2
{}

內置的函數globals返回一個包含所有python解釋器知道的變量名稱的字典(為了干凈和洗的白白的,我省略了python自行創建的一些變量)。在#2我調用了函數 foo 把函數內部本地作用域里面的內容打印出來。我們能夠看到,函數foo有自己獨立的命名空間,雖然暫時命名空間里面什么都還沒有。

3.變量解析規則

在python的作用域規則里面,優先在局部作用域里查找,找不到再去找全局作用域。

a_string = "This is a global variable"
def foo():
    print a_string # 1
foo()
This is a global variable

在#1處,python解釋器會嘗試查找變量a_string,當然在函數的本地作用域里面是找不到的,所以接著會去上層的作用域里面去查找。
但是另一方面,假如我們在函數內部給全局變量賦值,結果卻和我們想的不一樣:

a_string = "This is a global variable"
def foo():
    a_string = "test" # 1
    print locals()
foo()
{'a_string': 'test'}
a_string # 2
'This is a global variable'

lst = []
def foo1():
    lst.append(233)
    print locals()
foo1() # 3
{}
lst # 4
[233]
  • 我們能夠看到,全局變量能夠被訪問到(如果是可變數據類型(像list,dict這些)甚至能夠被更改)但是賦值不行。
  • 在函數內部的#1處,我們實際上新創建了一個局部變量,隱藏全局作用域中的同名變量。我們可以通過打印出局部命名空間中的內容得出這個結論。我們也能看到在#2處打印出來的變量a_string的值并沒有改變。
  • 在#3處調用函數會改動全局變量lst。因為lst沒有賦值操作,局部作用域也是空的。
    額外多說點,如果在函數內就是想要對全局變量直接賦值的話,可以在函數內注明使用的是全局變量:
a_string = "This is a global variable"
def foo():
    global a_string
    a_string = "this is a local variable" # 1
    print locals()
foo()
{}
a_string # 2
'this is a local variable'

4.變量生存周期

值得注意的一個點是,變量不僅是生存在一個個的命名空間內,他們都有自己的生存周期,請看下面這個例子:

def foo():
    x = 1
foo()
print x # 1
#Traceback (most recent call last):
#NameError: name 'x' is not defined

1處發生的錯誤不僅僅是因為作用域規則導致的(盡管這是拋出了NameError的錯誤的原因)它還和python以及其它很多編程語言中函數調用實現的機制有關。在這個地方這個執行時間點并沒有什么有效的語法讓我們能夠獲取變量x的值,因為它這個時候壓根不存在!

函數foo的命名空間隨著函數調用開始而開始,結束而銷毀。

5.函數參數

python允許我們向函數傳遞參數,參數會變成本地變量存在于函數內部。

def foo(x):
    print locals()
foo(1)
{'x': 1}

在Python里有很多的方式來定義和傳遞參數,完整版可以查看 python官方文檔。我們這里簡略的說明一下:函數的參數可以是必須的位置參數或者是可選的命名,默認參數。

def foo(x, y=0): # 1
    return x - y
foo(3, 1) # 2
2
foo(3) # 3
3
foo() # 4
#Traceback (most recent call last):
#TypeError: foo() takes at least 1 argument (0 given)
foo(y=1, x=3) # 5
2

在#1處我們定義了函數foo,它有一個位置參數x和一個命名參數y。在#2處我們能夠通過常規的方式來調用函數,盡管有一個命名參數,但參數依然可以通過位置傳遞給函數。在調用函數的時候,對于命名參數y我們也可以完全不管就像#3處所示的一樣。如果命名參數沒有接收到任何值的話,python會自動使用聲明的默認值也就是0。需要注意的是我們不能省略第一個位置參數x, 否則的話就會像#4處所示發生錯誤。
函數的參數可以有名稱和位置。這意味著在函數的定義和調用的時候會稍稍在理解上有點兒不同。我們可以給只定義了位置參數的函數傳遞命名參數(實參),反之亦然!如果覺得不夠可以查看官方文檔

6. 嵌套函數

Python允許創建嵌套函數。這意味著我們可以在函數里面定義函數而且現有的作用域和變量生存周期依舊適用。

def outer():
    x = 1
    def inner():
        print x # 1
    return inner() # 2
outer()
1

這個例子有一點兒復雜,但是看起來也還行。想一想在#1發生了什么:python解釋器需找一個叫x的本地變量,查找失敗之后會繼續在上層的作用域里面尋找,這個上層的作用域定義在另外一個函數里面。對函數outer來說,變量x是一個本地變量,但是如先前提到的一樣,函數inner可以訪問封閉的作用域(至少可以讀和修改)。在#2處,我們調用函數inner,非常重要的一點是,inner也僅僅是一個遵循python變量解析規則的變量名,python解釋器會優先在outer的作用域里面對變量名inner查找匹配的變量.

7. 函數是python世界里的一級類對象

一等公民,不想Java那樣函數都是放在類里的

顯而易見,在python里函數和其他東西一樣都是對象。(此處應該大聲歌唱)啊!包含變量的函數,你也并不是那么特殊!

issubclass(int, object) # all objects in Python inherit from a common baseclass
#True
def foo():
    pass
foo.__class__ # 1
#<type 'function'>
issubclass(foo.__class__, object)
#True

你也許從沒有想過,你定義的函數居然會有屬性。沒辦法,函數在python里面就是對象,和其他的東西一樣,也許這樣描述會太學院派太官方了點:在python里,函數只是一些普通的值而已和其他的值一毛一樣。這就是說你尅一把函數想參數一樣傳遞給其他的函數或者說從函數了里面返回函數!如果你從來沒有這么想過,那看看下面這個例子:

def add(x, y):
    return x + y
def sub(x, y):
    return x - y
def apply(func, x, y): # 1
    return func(x, y) # 2
apply(add, 2, 1) # 3
3
apply(sub, 2, 1)
1

你們也許看到過這樣的行為:“python把頻繁要用的操作變成函數作為參數進行使用,像通過傳遞一個函數給內置排序函數的key參數從而來自定義排序規則。那把函數當做返回值回事這樣的情況呢:

def outer():
    def inner():
        print "Inside inner"
    return inner # 1
foo = outer() #2
foo
#<function inner at 0x...>
foo()
#Inside inner

這個例子看起來也許會更加的奇怪。在#1處我把恰好是函數標識符的變量inner作為返回值返回出來。這并沒有什么特殊的語法:”把函數inner返回出來,否則它根本不可能會被調用到。“還記得變量的生存周期嗎?每次函數outer被調用的時候,函數inner都會被重新定義,如果它不被當做變量返回的話,每次執行過后它將不復存在。

在#2處我們捕獲住返回值 – 函數inner,將它存在一個新的變量foo里。我們能夠看到,當對變量foo進行求值,它確實包含函數inner,而且我們能夠對他進行調用。初次看起來可能會覺得有點奇怪,但是理解起來并不困難是吧。堅持住,因為奇怪的轉折馬上就要來了

8. 閉包(被函數記住的封閉作用域)

我們先不急著定義什么是閉包,先來看看一段代碼,僅僅是把上一個例子簡單的調整了一下:

def outer():
    x = 1
    def inner():
        print x # 1
    return inner
foo = outer()
foo.func_closure
#(<cell at 0x...: int object at 0x...>,)

在上一個例子中我們了解到,inner作為一個函數被outer返回,保存在一個變量foo,并且我們能夠對它進行調用foo()。不過它會正常的運行嗎?我們先來看看作用域規則。

所有的東西都在python的作用域規則下進行工作:“x是函數outer里的一個局部變量。當函數inner在#1處打印x的時候,python解釋器會在inner內部查找相應的變量,當然會找不到,所以接著會到封閉作用域里面查找,并且會找到匹配。

但是從變量的生存周期來看,該怎么理解呢?我們的變量x是函數outer的一個本地變量,這意味著只有當函數outer正在運行的時候才會存在。根據我們已知的python運行模式,我們沒法在函數outer返回之后繼續調用函數inner,在函數inner被調用的時候,變量x早已不復存在,可能會發生一個運行時錯誤。

萬萬沒想到,返回的函數inner居然能夠正常工作。Python支持一個叫做函數閉包的特性,用人話來講就是,嵌套定義在非全局作用域里面的函數能夠記住它在被定義的時候它所處的封閉命名空間。這能夠通過查看函數的func_closure屬性得出結論,這個屬性里面包含封閉作用域里面的值(只會包含被捕捉到的值,比如x,如果在outer里面還定義了其他的值,封閉作用域里面是不會有的)
記住,每次函數outer被調用的時候,函數inner都會被重新定義。現在變量x的值不會變化,所以每次返回的函數inner會是同樣的邏輯,假如我們稍微改動一下呢?

def outer(x):
    def inner():
        print x # 1
    return inner
print1 = outer(1)
print2 = outer(2)
print1()
1
print2()
2

從這個例子中你能夠看到閉包 – 被函數記住的封閉作用域 – 能夠被用來創建自定義的函數,本質上來說是一個硬編碼的參數。事實上我們并不是傳遞參數1或者2給函數inner,我們實際上是創建了能夠打印各種數字的各種自定義版本。

閉包單獨拿出來就是一個非常強大的功能, 在某些方面,你也許會把它當做一個類似于面向對象的技術:outer像是給inner服務的構造器,x像一個私有變量。使用閉包的方式也有很多:你如果熟悉python內置排序方法的參數key,你說不定已經寫過一個lambda方法在排序一個列表的列表的時候基于第二個元素而不是第一個。現在你說不定也可以寫一個itemgetter方法,接收一個索引值來返回一個完美的函數,傳遞給排序函數的參數key。

不過,我們現在不會用閉包做這么low的事(⊙o⊙)…!相反,讓我們再爽一次,寫一個高大上的裝飾器!

9. 裝飾器

裝飾器其實就是一個閉包,把一個函數當做參數然后返回一個替代版函數。我們一步步從簡到繁來瞅瞅:

def outer(some_func):
    def inner():
        print "before some_func"
        ret = some_func() # 1
        return ret + 1
    return inner
def foo():
    return 1
decorated = outer(foo) # 2
decorated()
#before some_func
#2

仔細看看上面這個裝飾器的例子。們定義了一個函數outer,它只有一個some_func的參數,在他里面我們定義了一個嵌套的函數inner。inner會打印一串字符串,然后調用some_func,在#1處得到它的返回值。在outer每次調用的時候some_func的值可能會不一樣,但是不管some_func的之如何,我們都會調用它。最后,inner返回some_func() + 1的值 – 我們通過調用在#2處存儲在變量decorated里面的函數能夠看到被打印出來的字符串以及返回值2,而不是期望中調用函數foo得到的返回值1。

我們可以認為變量decorated是函數foo的一個裝飾版本,一個加強版本。事實上如果打算寫一個有用的裝飾器的話,我們可能會想愿意用裝飾版本完全取代原先的函數foo,這樣我們總是會得到我們的”加強版“foo。想要達到這個效果,完全不需要學習新的語法,簡單地賦值給變量foo就行了:

foo = outer(foo)
foo # doctest: +ELLIPSIS
#<function inner at 0x...>

現在,任何怎么調用都不會牽扯到原先的函數foo,都會得到新的裝飾版本的foo。

假設有如下函數:

from datetime import datetime
def now():
    print str(datetime.now())
f = now
f()
#'2018-11-11 14:06:07.600773'

現在假設我們要增強now()函數的功能,比如,在函數調用前后自動打印日志,但又不希望修改now()函數的定義,這種在代碼運行期間動態增加功能的方式,稱之為“裝飾器”(Decorator)。

本質上,decorator就是一個返回函數的高階函數。所以,我們要定義一個能打印日志的decorator,可以定義如下:

def log(func):
    def wrapper(*args, **kwargs):
        print 'call {}'.format(func.__name__)
        ret = func(*args, **kwargs)
        return ret
    return wrapper
def pp(a):
    print a
pp = log(pp)
pp(2)
#call pp
#2

觀察上面的log,因為它是一個decorator,所以接受一個函數作為參數,并返回一個函數。

10.使用 @ 標識符將裝飾器應用到函數

在上一節的例子里我們是將原本的方法用裝飾后的方法代替:

pp = log(pp)

Python2.4支持使用標識符@將裝飾器應用在函數上,只需要在函數的定義前加上@和裝飾器的名稱。

@log
def pp(a):
    print a

需要明白的是,這樣的做法和先前簡單的用包裝方法替代原有方法是一模一樣的, python只是加了一些語法糖讓裝飾的行為更加的直接明確和優雅一點。
多個decorator怎么處理呢?

@decorator_one
@decorator_two
def func():
    pass

相當于:

func = decorator_one(decorator_two(func))

本身帶參數的decorator:

@decorator(arg1, arg2)
def func():
    pass

相當于

func = decorator(arg1,arg2)(func)

這意味著decorator(arg1, arg2)這個函數需要返回一個“真正的decorator”。

11. *args and **kwargs

decorator(arg1, arg2)是一個有用的裝飾器,但是由于硬編碼的原因它只能應用在一類具體的方法上,這類方法接收兩個參數,傳遞給閉包捕獲的函數。如果我們想實現一個能夠應用在任何方法上的裝飾器要怎么做呢?再比如,如果我們要實現一個能應用在任何方法上的類似于計數器的裝飾器,不需要改變原有方法的任何邏輯。這意味著裝飾器能夠接受擁有任何簽名的函數作為自己的被裝飾方法,同時能夠用傳遞給它的參數對被裝飾的方法進行調用。
非常巧合的是Python正好有支持這個特性的語法。可以閱讀 Python Tutorial 獲取更多的細節。當定義函數的時候使用了,意味著那些通過位置傳遞的參數將會被放在帶有前綴的變量中,
arg在函數內是元組類型。 所以:

def one(*args):
    print args # 1
one()
#()
one(1, 2, 3)
#(1, 2, 3)
def two(x, y, *args): # 2
    print x, y, args
two('a', 'b', 'c')
#a b ('c',)
def add(x, y):
    return x + y
lst = [1,2]
add(lst[0], lst[1]) # 1
3
add(*lst) # 2
3

接下來提到的會稍多更復雜一點,代表著鍵值對的參數字典,和*所代表的意義相差無幾,也很簡單對不對:

def foo(**kwargs):
    print kwargs
foo()
#{}
foo(x=1, y=2)
#{'y': 2, 'x': 1}

當我們定義一個函數的時候,我們能夠用kwargs來表明,所有未被捕獲的關鍵字參數都應該存儲在kwargs的字典中。如前所訴,args、kwargs并不是python語法的一部分,但在定義函數的時候,使用這樣的變量名算是一個不成文的約定。和一樣,我們同樣可以在定義或者調用函數的時候使用*。

dct = {'x': 1, 'y': 2}
def bar(x, y):
    return x + y
bar(**dct)
#3

12. 更通用的裝飾器

有了這招新的技能,我們隨隨便便就可以寫一個能夠記錄下傳遞給函數參數的裝飾器了。先來個簡單地把日志輸出到界面的例子

def logger(func):
    def inner(*args, **kwargs): #1
        print "Arguments were: %s, %s" % (args, kwargs)
        return func(*args, **kwargs) #2
    return inner

請注意我們的函數inner,它能夠接受任意數量和類型的參數并把它們傳遞給被包裝的方法,這讓我們能夠用這個裝飾器來裝飾任何方法

@logger
def foo1(x, y=1):
    return x * y
@logger
def foo2():
    return 2
foo1(5, 4)
#Arguments were: (5, 4), {}
#20
foo1(1)
#Arguments were: (1,), {}
#1
foo2()
#Arguments were: (), {}
#2

13. 帶參數的裝飾器

如果decorator本身需要傳入參數,那就需要編寫一個返回decorator的高階函數,寫出來會更復雜。比如,要自定義log的文本:

def log(text):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print '{} {}'.format(text, func.__name__)
            ret = func(*args, **kwargs)
            return ret
        return wrapper
    return decorator

這個3層嵌套的decorator用法如下:

@log('execute')
def now():
    print '2013-12-25'

now()
#execute now():
#2013-12-25

和兩層嵌套的decorator相比,3層嵌套的效果是這樣的:

now = log('execute')(now)

來剖析上面的語句,首先執行log('execute'),返回的是decorator函數,再調用返回的函數,參數是now函數,返回值最終是wrapper函數。

14.裝飾器的副作用

以上兩種decorator的定義都沒有問題,但還差最后一步。因為我們講了函數也是對象,它有name等屬性,但你去看經過decorator裝飾之后的函數,它們的name已經從原來的'now'變成了'wrapper':

>>> now.__name__
'wrapper'

因為返回的那個wrapper()函數名字就是'wrapper',所以,需要把原始函數的name等屬性復制到wrapper()函數中,否則,有些依賴函數簽名的代碼執行就會出錯。

def log(func):
    def wrapper(*args, **kw):
        print 'call %s():' % func.__name__
        return func(*args, **kw)
    wrapper.__name__ = func.__name__
    return wrapper

其實不需要編寫wrapper.name = func.name這樣的代碼,Python內置的functools.wraps就是干這個事的,所以,一個完整的decorator的寫法如下:

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        print 'call %s():' % func.__name__
        return func(*args, **kw)
    return wrapper

或者針對帶參數的decorator:

from functools import wraps
def log(text):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print '{} {}'.format(text, func.__name__)
            ret = func(*args, **kwargs)
            return ret
        return wrapper
    return decorator

import functools是導入functools模塊。模塊的概念稍候講解。現在,只需記住在定義wrapper()的前面加上@functools.wraps(func)即可。

當然,即使是你用了functools的wraps,也不能完全消除這樣的副作用。你會發現,即使是你你用了functools的wraps,你在用getargspec時,參數也不見了。要修正這一問題,我們還得用Python的反射來解決,當然,我相信大多數人的程序都不會去getargspec。所以,用functools的wraps應該夠用了。

class式的 Decorator

把一個類用來做裝飾器用,其實讓類本身可調用(含call方法)就是跟函數形式的裝飾器沒差啊。

class MyDecorator(object):
 
    def __init__(self, fn):
        print "inside myDecorator.__init__()"
        self.fn = fn
 
    def __call__(self):
        self.fn()
        print "inside myDecorator.__call__()"
 
@MyDecorator
def aFunction():
    print "inside aFunction()"
 
print "Finished decorating aFunction()"
 
aFunction()
 
# 輸出:
# inside myDecorator.__init__()
# Finished decorating aFunction()
# inside aFunction()
# inside myDecorator.__call__()

1)一個是init(),這個方法是在我們給某個函數decorator時被調用,所以,需要有一個fn的參數,也就是被decorator的函數。
2)一個是call(),這個方法是在我們調用被decorator函數時被調用的。
上面輸出可以看到整個程序的執行順序。

這看上去要比“函數式”的方式更易讀一些。

上面這段代碼中,我們需要注意這幾點:

1)如果decorator有參數的話,init() 成員就不能傳入fn了而是傳入參數,而fn是在call的時候傳入的。

16. 一些decorator的示例

  • 1.函數調用做緩存
    整個網上都用這個例子做decorator的經典范例
from functools import wraps
def memo(fn):
    cache = {}
 
    @wraps(fn)
    def wrapper(*args):
        result = cache.get(args)
        if result is None:
            result = fn(*args)
            cache[args] = result
        return result
 
    return wrapper
 
@memo
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

上面這個例子中,是一個斐波拉契數例的遞歸算法。我們知道,這個遞歸是相當沒有效率的,因為會重復調用。比如:我們要計算fib(5),于是其分解成fib(4) + fib(3),而fib(4)分解成fib(3)+fib(2),fib(3)又分解成fib(2)+fib(1)…… 你可看到,基本上來說,fib(3), fib(2), fib(1)在整個遞歸過程中被調用了兩次。

而我們用decorator,在調用函數前查詢一下緩存,如果沒有才調用了,有了就從緩存中返回值。一下子,這個遞歸從二叉樹式的遞歸成了線性的遞歸。

另外一個常見的例子是爬蟲里的 URL Cache:

import urllib

def web_lookup(url, saved={}):
    if url in saved:
        return saved[url]
    page = urllib.urlopen(url).read()
    saved[url] = page
    return page

可以這樣寫:

@cache
def web_lookup(url):
    return urllib.urlopen(url).read()

def cache(func):
    saved = {}
    @wraps(func)
    def newfunc(*args):
        if args in saved:
            return saved[args]
        result = func(*args)
        saved[args] = result
        return result
    return newfunc
  • 2.Profiler的例子
import cProfile, pstats, StringIO
 
def profiler(func):
    def wrapper(*args, **kwargs):
        datafn = func.__name__ + ".profile" # Name the data file
        prof = cProfile.Profile()
        retval = prof.runcall(func, *args, **kwargs)
        #prof.dump_stats(datafn)
        s = StringIO.StringIO()
        sortby = 'cumulative'
        ps = pstats.Stats(prof, stream=s).sort_stats(sortby)
        ps.print_stats()
        print s.getvalue()
        return retval
 
    return wrapper
  • 3.注冊回調函數
    下面這個示例展示了通過URL的路由來調用相關注冊的函數示例:
class MyApp():
    def __init__(self):
        self.func_map = {}
 
    def register(self, name):
        def func_wrapper(func):
            self.func_map[name] = func
            return func
        return func_wrapper
 
    def call_method(self, name=None):
        func = self.func_map.get(name, None)
        if func is None:
            raise Exception("No function registered against - " + str(name))
        return func()
 
app = MyApp()
 
@app.register('/')
def main_page_func():
    return "This is the main page."
 
@app.register('/next_page')
def next_page_func():
    return "This is the next page."
 
print app.call_method('/')
print app.call_method('/next_page')

注意:
1)上面這個示例中,用類的實例來做decorator。
2)decorator類中沒有call(),但是wrapper返回了原函數。所以,原函數沒有發生任何變化。

  • 4.給函數打日志
    下面這個示例演示了一個logger的decorator,這個decorator輸出了函數名,參數,返回值,和運行時間。
from functools import wraps
def logger(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        ts = time.time()
        result = fn(*args, **kwargs)
        te = time.time()
        print "function      = {0}".format(fn.__name__)
        print "    arguments = {0} {1}".format(args, kwargs)
        print "    return    = {0}".format(result)
        print "    time      = %.6f sec" % (te-ts)
        return result
    return wrapper
 
@logger
def multipy(x, y):
    return x * y
 
@logger
def sum_num(n):
    s = 0
    for i in xrange(n+1):
        s += i
    return s
 
print multipy(2, 10)
print sum_num(100)
print sum_num(10000000)

上面那個打日志還是有點粗糙,讓我們看一個更好一點的(帶log level參數的):

import inspect
def get_line_number():
    return inspect.currentframe().f_back.f_back.f_lineno
 
def logger(loglevel):
    def log_decorator(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            ts = time.time()
            result = fn(*args, **kwargs)
            te = time.time()
            print "function   = " + fn.__name__,
            print "    arguments = {0} {1}".format(args, kwargs)
            print "    return    = {0}".format(result)
            print "    time      = %.6f sec" % (te-ts)
            if (loglevel == 'debug'):
                print "    called_from_line : " + str(get_line_number())
            return result
        return wrapper
    return log_decorator

@logger('debug')
def multipy(x, y):
    return x * y

但是,上面這個帶log level參數的有兩具不好的地方,
1) loglevel不是debug的時候,還是要計算函數調用的時間。
2) 不同level的要寫在一起,不易讀。

import inspect
 
def advance_logger(loglevel):
 
    def get_line_number():
        return inspect.currentframe().f_back.f_back.f_lineno
 
    def _basic_log(fn, result, *args, **kwargs):
        print "function   = " + fn.__name__,
        print "    arguments = {0} {1}".format(args, kwargs)
        print "    return    = {0}".format(result)
 
    def info_log_decorator(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            result = fn(*args, **kwargs)
            _basic_log(fn, result, args, kwargs)
        return wrapper
 
    def debug_log_decorator(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            ts = time.time()
            result = fn(*args, **kwargs)
            te = time.time()
            _basic_log(fn, result, args, kwargs)
            print "    time      = %.6f sec" % (te-ts)
            print "    called_from_line : " + str(get_line_number())
        return wrapper
 
    if loglevel is "debug":
        return debug_log_decorator
    else:
        return info_log_decorator

你可以看到兩點,
1)我們分了兩個log level,一個是info的,一個是debug的,然后我們在外尾根據不同的參數返回不同的decorator。
2)我們把info和debug中的相同的代碼抽到了一個叫_basic_log的函數里,DRY原則。

    1. 一個MySQL的Decorator
      轉載作者用過下面這個例子于工作中,可以將sql語句作為裝飾器本身的參數來達到函數僅僅關注對sql查詢結果處理的效果
import umysql
from functools import wraps
 
class Configuraion:
    def __init__(self, env):
        if env == "Prod":
            self.host    = "coolshell.cn"
            self.port    = 3306
            self.db      = "coolshell"
            self.user    = "coolshell"
            self.passwd  = "fuckgfw"
        elif env == "Test":
            self.host   = 'localhost'
            self.port   = 3300
            self.user   = 'coolshell'
            self.db     = 'coolshell'
            self.passwd = 'fuckgfw'
 
def mysql(sql):
 
    _conf = Configuraion(env="Prod")
 
    def on_sql_error(err):
        print err
        sys.exit(-1)
 
    def handle_sql_result(rs):
        if rs.rows > 0:
            fieldnames = [f[0] for f in rs.fields]
            return [dict(zip(fieldnames, r)) for r in rs.rows]
        else:
            return []
 
    def decorator(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            mysqlconn = umysql.Connection()
            mysqlconn.settimeout(5)
            mysqlconn.connect(_conf.host, _conf.port, _conf.user, \
                              _conf.passwd, _conf.db, True, 'utf8')
            try:
                rs = mysqlconn.query(sql, {})
            except umysql.Error as e:
                on_sql_error(e)
 
            data = handle_sql_result(rs)
            kwargs["data"] = data
            result = fn(*args, **kwargs)
            mysqlconn.close()
            return result
        return wrapper
 
    return decorator
 
@mysql(sql = "select * from coolshell" )
def get_coolshell(data):
    ... ...
    ... ..
  • 6.線程異步
    下面是個非常簡單的異步執行的decorator,注意,異步處理并不簡單,下面只是一個示例。
from threading import Thread
from functools import wraps
 
def async(func):
    @wraps(func)
    def async_func(*args, **kwargs):
        func_hl = Thread(target = func, args = args, kwargs = kwargs)
        func_hl.start()
        return func_hl
 
    return async_func
 
if __name__ == '__main__':
    from time import sleep
 
    @async
    def print_somedata():
        print 'starting print_somedata'
        sleep(2)
        print 'print_somedata: 2 sec passed'
        sleep(2)
        print 'print_somedata: 2 sec passed'
        sleep(2)
        print 'finished print_somedata'
 
    def main():
        print_somedata()
        print 'back in main'
        print_somedata()
        print 'back in main'
 
    main()
  • 7.超時函數
    這個函數的作用在于可以給任意可能會hang住的函數添加超時功能,這個功能在編寫外部API調用 、網絡爬蟲、數據庫查詢的時候特別有用。

timeout裝飾器的代碼如下:

# coding=utf-8
# 測試utf-8編碼
import sys

reload(sys)
sys.setdefaultencoding('utf-8')

import signal, functools

class TimeoutError(Exception): pass

def timeout(seconds, error_message="Timeout Error: the cmd 30s have not finished."):
    def decorated(func):
        result = ""

        def _handle_timeout(signum, frame):
            global result
            result = error_message
            raise TimeoutError(error_message)

        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            global result
            signal.signal(signal.SIGALRM, _handle_timeout)
            signal.alarm(seconds)

            try:
                result = func(*args, **kwargs)
            finally:
                signal.alarm(0)
                return result
            return result

        return wrapper

    return decorated

@timeout(2)  # 限定下面的slowfunc函數如果在2s內不返回就強制拋TimeoutError Exception結束
def slowfunc(sleep_time):
    a = 1
    import time
    time.sleep(sleep_time)
    return a

# slowfunc(3) #sleep 3秒,正常返回 沒有異常

print slowfunc(11)  # 被終止
  • 8.Trace函數

有時候出于演示目的或者調試目的,我們需要程序運行的時候打印出每一步的運行順序 和調用邏輯。類似寫bash的時候的bash -x調試功能,然后Python解釋器并沒有內置這個有用的功能,那么我們就“自己動手,豐衣足食”。
這個例子看出轉載作者對源碼還是挺了解的

Trace裝飾器的代碼如下:

# coding=utf-8
# 測試utf-8編碼
import sys
reload(sys)
sys.setdefaultencoding('utf-8')

import sys,os,linecache
def trace(f):
  def globaltrace(frame, why, arg):
    if why == "call": return localtrace
    return None
  def localtrace(frame=1, why=2, arg=4):
    if why == "line":
      # record the file name and line number of every trace
      filename = frame.f_code.co_filename
      lineno = frame.f_lineno
      bname = os.path.basename(filename)
      print "{}({}): {}".format(  bname,
                    lineno,
                    linecache.getline(filename, lineno)),
    return localtrace
  def _f(*args, **kwds):
    sys.settrace(globaltrace)
    result = f(*args, **kwds)
    sys.settrace(None)
    return result
  return _f

@trace
def xxx():
  a=1
  print a
  print 22
  print 333

xxx() #調用

#######################################
C:\Python27\python.exe F:/sourceDemo/flask/study/com.test.bj/t2.py
t2.py(31):   a=1
t2.py(32):   print a
1
t2.py(33):   print 22
22
t2.py(34):   print 333
333

Process finished with exit code 0
  • 9.單例模式
# coding=utf-8
# 測試utf-8編碼
# 單例裝飾器
import sys
reload(sys)
sys.setdefaultencoding('utf-8')

# 使用裝飾器實現簡單的單例模式
def singleton(cls):
    instances = dict()  # 初始為空
    def _singleton(*args, **kwargs):
        if cls not in instances:  #如果不存在, 則創建并放入字典
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return _singleton

@singleton
class Test(object):
    pass
if __name__ == '__main__':
    t1 = Test()
    t2 = Test()
    # 兩者具有相同的地址
    print t1
    print t2
  • 10.LRUCache
    下面要分享的這個LRUCache不是我做的,是github上的一個庫,我們在實際環境中有用到。

先來說下這個概念,cache的意思就是緩存,LRU就是Least Recently Used,即最近最少使用,是一種內存管理算法。總結來說這就是一種緩存方法,基于時間和容量。
一般在簡單的python程序中,遇到需要處理緩存的情況時最簡單的方式,聲明一個全局的dict就能解決(在python中應盡量避免使用全局變量)。但是只是簡單情況,這種情況會帶來的問題就是內存泄漏,因為可能會出現一直不命中的情況。
由此導致的一個需求就是,你要設定這個dict的最大容量,防止發生泄漏。但僅僅是設定最大容量是不夠的,設想當你的dict變量已被占滿,還是沒有命中,該如何處理。
這時就需要加一個失效時間了。如果在指定失效期內沒有使用到該緩存,則刪除。

綜述上面的需求和功能就是LRUCache干的事了。不過這份代碼做了更進一層的封裝,可以讓你直接把緩存功能做為一個裝飾器來用。具體實現可以去參考代碼,別人實現之后看起來并不復雜 :)

from lru import lru_cache_function

@lru_cache_function(max_size=1024, expiration=15*60)
def f(x):
    print "Calling f(" + str(x) + ")"
    return x

f(3) # This will print "Calling f(3)", will return 3
f(3) # This will not print anything, but will return 3 (unless 15 minutes have passed between the first and second function call).

代碼: https://github.com/the5fire/Python-LRU-cache/blob/master/lru.py

從python3.2開始內置在functools了lru_cache的功能,說明這個需求是很普遍的。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,559評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,442評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,581評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,922評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,096評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,639評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,374評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,591評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,789評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,322評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,554評論 2 379

推薦閱讀更多精彩內容

  • 呵呵!作為一名教python的老師,我發現學生們基本上一開始很難搞定python的裝飾器,也許因為裝飾器確實很難懂...
    TypingQuietly閱讀 19,580評論 26 186
  • 原文出處: dzone 譯文出處:Wu Cheng(@nullRef) 1. 函數 在python中,函數通過...
    DraculaWong閱讀 535評論 0 3
  • 在學習Python的過程中,我相信有很多人和我一樣,對Python的裝飾器一直覺得很困惑,我也是困惑了好久,并通過...
    lijincheng閱讀 5,939評論 0 5
  • 在Python中,裝飾器一般用來修飾函數,實現公共功能,達到代碼復用的目的。在函數定義前加上@xxxx,然后函數就...
    fasionchan閱讀 2,206評論 0 6
  • 以前你有沒有這樣一段經歷:很久之前你寫過一個函數,現在你突然有了個想法就是你想看看,以前那個函數在你數據集上的運行...
    秋殤灬閱讀 377評論 0 1