python中的裝飾器和回調函數

1. 函數嵌套與裝飾器

在python中,函數可以作爲參數傳遞。裝飾器可以很方便的給原函數增加一些功能。使用裝飾器必須和內嵌包裝函數一起。

簡單來說在f2上@f1等價於執行f2 = f1(f2)

注意這裏是f2而不是f2(),也就是說是函數名作爲參數傳遞,不需要執行f2();而f1是f1(),所以f1在使用裝飾器的地方相當於調用過一次。如果f1是一個類,那麼相當於這裏要調用一次構造函數__init__和回調函數__call___。
下面舉幾個例子:

1.1 單純嵌套

def f1(g):
	print("in f1")
	return g

@f1
def f2():
	print("in f2")

f2()
f2()

在這個例子中@f1等價於f2 = f1(f2)= f2,此時打印了"in f1";接着執行兩遍f2()就是打印兩遍"in f2"。最終輸出爲:

in f1
in f2
in f2

1.2 內嵌包裝函數,保證裝飾器調用

def f1(g):
	print("in f1")
	def m():
		print("in m")
		return g()
	return m

@f1
def f2():
	print("in f2")

f2()
f2()

在這個例子中@f1等價於f2 = f1(f2)= m,此時打印了"in f1";接着執行兩遍f2()等價於執行m(),而m()返回f2()。因此返回了兩遍"in m in f2"。最終輸出爲:

in f1
in m
in f2
in m
in f2

1.3 裝飾器帶參數,需要兩層內嵌函數

def f1(arg):
    print("in f1")
    def m(g):
        print("in m")
        def n():
            print(arg)
            print("in n")
            return g()
        return n
    return m

@f1("para")
def f2():
    print("in f2")

f2()
f2()

在這個例子中@f1(“para”)等價於f2 = f1(“para”)(f2)=m(f2)=n,此時打印了"in f1 in m";注意n()返回g(),因此接着執行兩遍f2()等價於執行兩遍n()+g()。這個例子用了2層包裝函數,最終輸出爲:

in f1
in m
para
in n
in f2
para
in n
in f2

1.4 函數帶參數,需要使用args列表

def f1(func):
    print("in f1")
    def m(a, b):
        print("in m")
        print(func(a, b))
    return m
 
@f1
def f2(a, b):
    print("f2(%s,%s) called." % (a, b))
    return a + b
 
f2(1, 2)
f2(3, 4)

這裏,@f1表示f2=f1(f2)=m,因此f2(a,b)=m(a,b)。
如果參數列表個數不確定怎麼辦?那就需要用到*args或者**kargs了,前者將參數打包成tuple,後者打包成dict。下面是個例子:

def f1(func):
    print("in f1")
    def m(*args):
        print("in m")
        print(args)
        print(func(*args))
    return m
 
@f1
def f2(a, b):
    print("f2(%s,%s) called." % (a, b))
    return a + b

@f1
def f3(a, b, c):
    print("f3(%s,%s,%s) called." % (a, b, c))
    return a + b + c
 
f2(1, 2)
f3(3, 4, 5)

返回

in f1
in f1
in m
(1, 2)
f2(1,2) called.
3
in m
(3, 4, 5)
f3(3,4,5) called.
12

1.5 類作爲裝飾器

class f1:
    def __init__(self,para):
        self.para = para
        print("in __init__")

    def __call__(self,p):
        print ("in __call__")
        def m():
            print("in m")
            return p(self.para)
        return m
 
@f1("parameters")
def f2(s):
    print(s)
    print ("in f2")

f2()
f2()

在這裏,@f1(“para”) 等價於f2= f1(“para”)(f2)=m,先執行f1的構造函數和回調函數。注意m返回f2(“para”),因此執行兩遍f2()變爲執行兩遍m()+f2(“para”)

2. 回調函數和閉包

上面涉及到回調函數和閉包兩個概念
回調函數是在每次調用類的時候啓動,可以使實例像函數一樣調用,比如f1是類f的一個實例,那麼f1(a,b)等於f1.__call__(a,b)。
在這裏插入圖片描述
回調實際上有兩種:阻塞式回調和延遲式回調。兩者的區別在於:阻塞式回調裏,回調函數的調用一定發生在起始函數返回之前;而延遲式回調裏,回調函數的調用有可能是在起始函數返回之後。

閉包指的是當某個函數被當成對象返回時,夾帶了外部變量,就形成了一個閉包。簡單來說,當定義內部嵌套函數時,返回的函數參數會自動定義__closure__屬性,裏面定義了一個元組用於存放所有的外面變量。

2.1 最簡單的閉包例子

先看一個最簡單的例子:

def f(a,b):
    def m():
        return a+b
    return m

a = f(4,5)
for c in  a.__closure__:
    print(c.cell_contents)

f中嵌套了函數m,因此返回函數賦值給a後,有了__closure__參數列表,其值等於內部函數m引用的外部函數f的參數列表的值,結果爲:

4
5

2.2 裝飾器和閉包

拿1.1節的例子來做示例:

def f1(g):
    def m():
        print("in m")
        return g()
    return m

@f1
def f2():
    print("in f2")

f2.__closure__[0].cell_contents()

@f1等價於執行f2 = f1(f2),f1內部有一個嵌套函數,因此f1(f2)返回的函數m有了__closure__參數列表,第一個參數的值是原始的f2函數。最後輸出

in f2

2.3 帶參數的裝飾器和閉包

以1.3節的例子爲基礎:

def f1(arg):
    def m(g):
        def n():
            k = arg
            return g()
        return n
    return m

@f1("para")
def f2():
    print("in f2")

print(f2.__closure__[0].cell_contents)
f2.__closure__[1].cell_contents()

這裏n是最內部嵌套函數,形成了一個閉包。@f1(“para”)返回n的clusure函數包含了arg和g兩個參數。上面的例子輸出爲:

para
in f2

2.4 回調函數和閉包

最後來看回調函數的閉包。以1.5節爲基礎例子:

class f1:
    def __init__(self,para):
        self.para = para

    def __call__(self,p):
        def m():
            return p(self.para)
        return m
 
@f1("parameters")
def f2(s):
    print(s)
    print ("in f2")

f2.__closure__[0].cell_contents("abc")
print(f2.__closure__[1].cell_contents.para)

f1在回調的時候包含了嵌套函數,因此在執行f2= f1(“para”)(f2)的時候,傳入了p和self兩個參數(注意p在前面),因此輸出爲:

abc
in f2
parameters
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章