1. 程式人生 > >Python的閉包和裝飾器

Python的閉包和裝飾器

閉包

變數作用域

作用域是程式執行時變數可以被引用的範圍。

  • 函式內部的變數被稱為區域性變數,它只能在函式內部中被引用。
  • 定義在程式碼最外層的變數為全域性變數,它可以整個程式碼中被引用。

在函式內部可以訪問全域性變數,在函式外部不能訪問區域性變數。

函式巢狀

把一個函式定義在另外一個函式的內部,就是函式巢狀。外邊的函式為外層函式,裡邊的函式為內層函式。

閉包

在函式巢狀中,內層函式對外層函式的區域性變數進行了引用,並且外層函式的返回值是內層函式的引用,就構成了一個閉包。

def outer(a, b):
    a = a
    b = b
    def inside():
        print(a+b)
    return inside


function_inside = outer(10, 20)
function_inside()

執行結果:

30
  • 函式是一個物件,可以作為返回值被返回,也可以作為引數被傳遞。
  • 函式執行完畢後變數會被回收,但是因為內層函式對外部函式的變數進行了引用,所以即使外層函式執行完畢,變數也不會被回收。
  • 修改外層函式的變數使用nonlocal關鍵字

    建立一個閉包

  • 閉包函式必須有函式巢狀
  • 內層函式需要引用外層函式中的變數
  • 閉包函式必須返回內層函式的引用

注意點

  1. 閉包就是函式和函式獨有的資料 結合在一起,它比類要輕量級,比一般的函式功能更強。

  2. 每次呼叫外層函式都會建立一個新的閉包物件。

  3. 內層函式沒有引用的變數會在外層函式執行結束後銷燬。

    裝飾器

問題提出

  1. 當電腦的硬碟空間不夠用的時候。
  2. 我們能想到的一個辦法就是把電腦拆開,再新增一塊硬碟。(功能增強)
  3. 但是這樣做,不僅麻煩,而且改變了原有的電腦結構。(複雜,改變了原有函式)
  4. 我們不想做改變原有電腦這麼複雜的事情,但是又要儲存更多東西。
  5. 所以聰明的人就發明了行動硬碟,它可以在不改變原有結構的情況下,又做了功能增強,並且還可以為被多個電腦使用,一臺電腦也可以使用多個行動硬碟。(功能增強,方便,不改變原有函式)

而我們需要給函式增加新的功能,又不想改變函式的程式碼的時候,便用到了 裝飾器

python函式

python函式可以被當作引數傳遞給其他函式。

def say(func):
    func()


def test():
    print("this is a function")


say(test)

執行結果:

this is a function

python的函式可以像變數一樣作為返回值返回,被定義在函式內部,而且還可以作為引數被傳遞。

裝飾器

原有函式:

def test():
    print("this is a function")

現在需要給函式增加另外一個功能,限制該函式的呼叫。

print("use limit")

把功能加入到函式內部

我們可以直接把功能加入到函式內部:

def test():
    print("use limit")
    print("this is a function")


test()

但是如果有函式test1、test2()...也想使用該功能,我們就需要一一修改,這就造成了:

  1. 修改工作量巨大
  2. 造成大量的重複程式碼
  3. 不利於以後功能的新增

定義一個新的函式

我們可以定義一個新的函式,來完成新增加的功能,把原有函式當作引數傳入新函式執行。

def limit(func):
    print("use limit")
    func()


def test():
    print("this is a function")


limit(test)

這樣不僅沒修改原有的函式,而且增加了新的功能,但是也存在新的問題:

  1. 呼叫的時候,不是在呼叫 test 而是呼叫新函式 limit
  2. 每次使用 test 函式的時候都需要呼叫 limit ,如果有已存在的呼叫,將無法使用新加入的功能,會對程式碼結構造成破環。

使用裝飾器

如果想對函式增加新的功能,並且不修改原有函式,且呼叫方式不做出改變的話,就要使用裝飾器。

簡單的裝飾器實現:

def limit(func):
    def addlimit():
        print("use limit")
        func()
    return addlimit


def test():
    print("this is a function")


test = limit(test)


test()

函式 limit 就是一個裝飾器,它把函式 test 當作引數傳入,在內層函式中增加功能後,又把內層函式返回,重新賦值給 test 變數。這裡使用到了閉包,外層函式負責接收要修飾的函式,返回修飾後的函式,內層函式賦值修飾傳入的函式。

裝飾器的進階

@語法糖

裝飾器的使用有一種簡寫方式,就是在函式定義之前使用 @ +裝飾器名字:

def limit(func):
    def addlimit():
        print("use limit")
        func()
    return addlimit

@limit  # 相當於test = limit(test)
def test():
    print("this is a function")


test()

@limit 便相當於 test = limit(test)

使用@語法糖 便相當於把定義在後邊的函式當作引數傳入裝飾器。
  • 可以省去最後一步再賦值的操作,使用方便。
  • 可以不修改原有的函式增加新的功能。
  • 不改變函式的呼叫方式。
  • 可以為多個函式進行裝飾。

被裝飾函式帶有引數

def limit(func):
    def addlimit(a, b):
        print("use limit")
        func(a, b)
    return addlimit


@limit
def test(a, b):
    print("I tell you : %s" % a)
    print("I tell you : %s" % b)


a = "hello"
b = "hi"
test(a, b)

因為裝飾器呼叫的是閉包中的內部函式,所以我們先在內部函式接收引數,再傳遞給被裝飾的函式。這樣,引數經過傳遞便被傳遞給了原有的 test 函式。

但是,如果其他函式不是兩個引數,在使用該裝飾器的時候,便會執行錯誤,為了裝飾器的通用性,我們可以用不定長位置引數 *args 和關鍵字引數 **kwargs

使用不定長引數

def limit(func):
    def addlimit(*args, **kwargs):
        print("use limit")
        func(*args, **kwargs)
    return addlimit


@limit
def test(a, b):
    print("I tell you : %s" % a)
    print("I tell you : %s" % b)


@limit
def test1(a, b, c):
    print("I tell you : %s" % a)
    print("I tell you : %s" % b)
    print("I tell you : %s" % c)


a = "hello"
b = "hi"

test(a, b)
print()
test1(a, b, c = "hello world")

執行結果:

use limit
I tell you : hello
I tell you : hi

use limit
I tell you : hello
I tell you : hi
I tell you : hello world

被裝飾函式帶有返回值

def limit(func):
    def addlimit():
        print("use limit")
        return func()
    return addlimit


@limit
def test():
    print("this is a function")
    return "I tell you: hello"


print(test())

直接在閉包內部函式返回原函式的呼叫結果即可。

既有引數又有返回值的通用裝飾器

def limit(func):
    def addlimit(*args, **kwargs):
        print("use limit")
        return func(*args, **kwargs)
    return addlimit

三層裝飾器

三層裝飾器可以在原有裝飾器的基礎上,設定額外的外部變數。

def limit_arg(arg):
    def limit(func):
        def addlimit():
            print("use limit--%s" % arg)
            func()
        return addlimit
    return limit


@limit_arg("hello")
def test():
    print("this is a function")


test()

執行結果:

use limit--hello
this is a function

test() 相當於:

limit_arg("hello")(test)()

類裝飾器

裝飾器不僅是一個函式,還可以是一個類。

class Limit(object):
    def __init__(self, func):
        self.func = func

    def __call__(self):
        print("use limit")
        self.func()


@Limit
def test():
    print("this is a function")


test()
  • 魔術方法 __call__ 方法可以讓類的例項物件像函式一樣被呼叫。

  • 類裝飾器對比函式裝飾器具有靈活度大、高內聚、封裝性等優點。

多個裝飾器

def add_a(func):
    def a():
        return "<a href='mxuanli.cn'>" + func() + "</a>" 
    return a


def add_h1(func):
    def h1():
        return "<h1>" + func() + "</h1>"
    return h1


@add_a
@add_h1
def test():
    return "hello world"


print(test())

執行結果:

<a href='mxuanli.cn'><h1>hello world</h1></a>
  • 多個裝飾器的時候,執行順序是從裡到外,會先從最內層的開始執行。