裝飾器-wrapper


我跟別人說我精通python,別人問我wrapper是啥,我說不知道,尼瑪,原來wrapper就是裝飾器,熟的不得了啊,英語真是我的克星啊。

 

閉包 closure

在認識裝飾器之前先認識下閉包

閉包,顧名思義就是把什么東西封閉在保內,什么東西呢?變量和函數。

在一個函數里裝了另一個函數,里面那個函數稱為內部函數,外面那個函數稱為外部函數,

在內部函數里,對在外部作用域(非全局作用域)里的變量進行引用,這個內部函數就稱為閉包

定義在外部函數內但被內部函數引用或調用的變量稱為自由變量,所以閉包又被稱為引用了自由變量的函數

 

內部函數和自由變量同時存在構建一個閉包實體,閉包的作用就是使閉包實體可以脫離創建它的環境運行,就是變量和函數脫離了創建它的環境依然存在,而且可執行,這點跟面向對象有些類似。

 

閉包的語法是一個函數A內創建了一個函數B,並且函數A返回了函數B,函數B就是閉包,函數A內的變量叫自由變量,包括A的參數

 

示例代碼

def counter(start_at=0):
    print('act')
    count=[start_at]        # 自由變量
    def incr():             # 內部函數
        count[0]+=1
        return count[0]
    return incr             # 返回一個函數對象

count=counter(5)            # act
print 111                   # 111
print count()   # 6         # count 和 incr 脫離了創建它的環境,依然可以運行
print count()   # 7

count2=counter(100)         # act
print 222                   # 222
print count2()  # 101
print count()   # 8

閉包的作用

1. 閉包實際上是對數據或者數據操作的封裝

2. 閉包可以實現一些通用的功能,它是裝飾器的基礎。

 

裝飾器

裝飾器本質上是個函數,一個用來包裝函數的函數,返回被包裝的函數對象。

被包裝的函數需要作為裝飾器函數的參數。

 

裝飾器以語法糖@開頭,形式如下

@decorator(dec_opt_args)
def func2Bdecorated(func_opt_args):

並且可以有多個裝飾器

@deco2
@deco1
def func():
    pass

等價於 func=deco2(deco1(func))

 

普通方式實現類似裝飾器的功能,以幫助理解

def deco(func):
    print("before myfunc() called.")
    func()
    print("  after myfunc() called.")
    return func

def myfunc():
    print(" myfunc() called.")

myfunc = deco(myfunc)
# before myfunc() called.
# myfunc() called.
# after myfunc() called.

myfunc()        #  myfunc() called.
myfunc()        #  myfunc() called.

 

使用語法糖@裝飾函數,注意這並不是裝飾器

def deco(func):
    print("before myfunc() called.")
    func()
    print("after myfunc() called.")
    return func

@deco
def myfunc():
    print("myfunc() called.")

myfunc()
# before myfunc() called.
# myfunc() called.
# after myfunc() called.
# myfunc() called.
print("*"*5)
myfunc()    #  myfunc() called.

可以看到第一次執行時@起了作用,第二次執行時@沒起作用。為什么呢,往下看

 

真正的裝飾器來了

無參裝飾器

def deco(func):
    print(33)
    def _deco():
        print("before myfunc() called.")
        func()
        print("  after myfunc() called.")
    return _deco

@deco
def myfunc():
    print(" myfunc() called.")
    return 'ok'

myfunc()
# 33
# before myfunc() called.
# myfunc() called.
# after myfunc() called.
myfunc()
# before myfunc() called.
# myfunc() called.
# after myfunc() called.

可以看到每次執行@都起了作用,但是33只被打印了一次,又是為什么呢?

明明是執行了兩次一模一樣的操作,結果卻不同,看來有必要深入理解一下裝飾器了。

 

深入理解裝飾器

之前講到閉包類似於面向對象,而裝飾器基於閉包,也應該類似於面向對象咯,或許吧,類似嘛,我又沒說是,所以應該沒錯,

為什么扯這么多,因為我要用class來解釋上面的問題。

 

對上面的無參裝飾器分析一

## 上述過程類似這樣
def myfunc():
    print(" myfunc() called.")
    return 'ok'

bb = deco(myfunc)       # 3
bb()
# before myfunc() called.
#  myfunc() called.
#   after myfunc() called.
bb()
# before myfunc() called.
#  myfunc() called.
#   after myfunc() called.

把裝飾器轉成普通函數,就明了了:

裝飾器內的操作在創建裝飾器實例時已經運行,這可以理解為class的實例化,如果在實例化時有print操作,在實例調用時不會再有

 

對上面的無參裝飾器分析二

def deco(func):
    a = 3
    def _deco():
        print("before myfunc() called.")
        func()
        print(a)
        print("  after myfunc() called.")
    return _deco

@deco
def myfunc():
    print(" myfunc() called.")
    return 'ok'

myfunc()
# before myfunc() called.
#  myfunc() called.
# 3
#   after myfunc() called.
myfunc()
# before myfunc() called.
#  myfunc() called.
# 3
#   after myfunc() called.

裝飾器傳遞的是自由變量和閉包,可以理解為class的實例屬性和方法,在實例調用時,屬性一直存在。

myfunc()第一次運行時相當於初始化了裝飾器,后面只是調用實例,雖然它沒有生成實例對象,在這點上不同於class。

 

總結

裝飾器函數真的類似於面向對象

裝飾器在第一次運行時相當於實例化class,實例化時可以有操作和屬性,操作不被傳遞,屬性被傳遞

裝飾器不需要創建實例對象,運行即相當於實例化class

裝飾器傳遞的是自由變量和屬性,裝飾器函數內的操作不被傳遞

 

裝飾器的各種語法

有參裝飾器

def deco(func):
    def _deco(a, b):
        print("before myfunc() called.")
        ret = func(a, b)
        print("  after myfunc() called. result: %s" % ret)
        return ret
    return _deco

@deco
def myfunc(a, b):
    print(" myfunc(%s,%s) called." % (a, b))
    return a + b

myfunc(1, 2)
# before myfunc() called.
#  myfunc(1,2) called.
#   after myfunc() called. result: 3
myfunc(3, 4)
# before myfunc() called.
#  myfunc(3,4) called.
#   after myfunc() called. result: 7

 

裝飾器帶參數

外面加一層

def deco(arg):
    def _deco(func):
        def __deco():
            print("before %s called [%s]." % (func.__name__, arg))
            func()
            print("  after %s called [%s]." % (func.__name__, arg))
        return __deco
    return _deco
 
@deco("mymodule")
def myfunc():
    print(" myfunc() called.")
 
@deco("module2")
def myfunc2():
    print(" myfunc2() called.")
 
myfunc()
myfunc2()

 

裝飾器帶類參數

裝飾器的參數是個類,也可以是實例,或者其他

class locker:
    def __init__(self):
        print("locker.__init__() should be not called.")
         
    @staticmethod
    def acquire():
        print("locker.acquire() called.(這是靜態方法)")
         
    @staticmethod
    def release():
        print("  locker.release() called.(不需要對象實例)")
 
def deco(cls):
    '''cls 必須實現acquire和release靜態方法'''
    def _deco(func):
        def __deco():
            print("before %s called [%s]." % (func.__name__, cls))
            cls.acquire()
            try:
                return func()
            finally:
                cls.release()
        return __deco
    return _deco
 
@deco(locker)
def myfunc():
    print(" myfunc() called.")
 
myfunc()
myfunc()

 

被裝飾的函數屬性發生了變化

def deco(func):
    def myfunc(x):
        return func(x)
    return myfunc

@deco
def test1(x):
    return x+1

print(test1(4))         # 5
print(test1.__name__)   # myfunc    名字並非真實名字

名字並非真正函數的名字,而是裝飾器函數里被裝飾的函數的名字

 

保留被裝飾的函數的屬性

import time
import functools

def timeit(func):
    @functools.wraps(func)   # 此句就是用來保留被裝飾的函數的屬性的 ,其余跟普通裝飾器一樣
    def wrapper():
        start = time.clock()
        func()
        end =time.clock()
        print 'used:', end - start
    return wrapper

@timeit
def foo():
    print 'in foo()'

foo()       # used: 5.13182184074e-06
print foo.__name__   # foo

name屬性被保留

 

 

參考資料:

http://www.cnblogs.com/rhcad/archive/2011/12/21/2295507.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM