Python——裝飾器 閉包的函數(shù)

閉包的定義

將組成函數(shù)的語句和這些語句的執(zhí)行環(huán)境打包在一起時,得到的對象稱為閉包
我們知道函數(shù)在Python中是第一類對象,也就是說可以把它們當作參數(shù)傳遞給其他函數(shù),放在數(shù)據(jù)結(jié)構(gòu)中,以及作為函數(shù)的返回結(jié)果。
我們定義一個函數(shù),它接受另一函數(shù)作為輸入并調(diào)用它

foo.py
def call(func):
    return func

我們在另一個程序中去調(diào)用這個函數(shù)

cal_close.py
from python_book import foo
def helloworld():
    return 'hello world'
print(foo.call(helloworld))

我們把函數(shù)當作數(shù)據(jù)處理時,它將隱式地攜帶與定義該函數(shù)的周圍環(huán)境相關(guān)的信息,這將影響到函數(shù)中自由變量的綁定方式。
我們在剛才的栗子中加入一個變量的定義

foo.py
x = 42
def call(func):
    return func

然后在調(diào)用函數(shù)的地方同樣也加入自己的函數(shù)定義

cal_close.py
from python_book import foo

x = 37

def helloworld():
    return 'hello world is %d' % x
print(foo.call(helloworld))
>>> hello world is 37

在這個栗子中,函數(shù)helloworld()使用的x的值是在與他相同的環(huán)境中定義的,即使在foo.py中也定義了一個變量x,而且這里也是實際調(diào)用helloworld()函數(shù)的地方,但x的值與helloworld()函數(shù)執(zhí)行時使用的x不同
事實上所有函數(shù)都擁有一個指向了定義該函數(shù)的全局命名空間的globals屬性,始終對應(yīng)于定義函數(shù)的閉包模塊,閉包將捕捉內(nèi)部函數(shù)執(zhí)行所需的整個環(huán)境
我們可以看一個閉包函數(shù)的栗子

def countdown(n):
    def next():
        nonlocal n
        r = n
        n -= 1
        return r
    return next
# 使用
next = countdown(10)
while True:
    v = next()
    if not v: break
    print(v)

在Python中,裝飾器是很重要的高級函數(shù)。要掌握裝飾器我們必須掌握相關(guān)的知識

函數(shù)作用域

LEGB原則

函數(shù)即對象

在Python中,函數(shù)和我們之前的數(shù)據(jù)類型等都一樣都是對象,而且函數(shù)是最高級的對象
在內(nèi)存中和對象一樣,也是將函數(shù)的指針存儲在函數(shù)名中


函數(shù)在內(nèi)存中的情況

既然函數(shù)和對象相同那么函數(shù)自然滿足對象的幾個條件
1.可以被賦值給其他變量

def foo():
    print('foo')
bar=foo
bar()
foo()
print(id(foo),id(bar))  #4321123592 4321123592

函數(shù)的函數(shù)名其實就是函數(shù)的指針,可以將函數(shù)的函數(shù)名賦值給其他的變量
2.其可以被定義在另外一個函數(shù)內(nèi)(作為參數(shù)&作為返回值):

#*******函數(shù)名作為參數(shù)**********
def foo(func):
    print('foo')
    func()
 
def bar():
    print('bar')
 
foo(bar)
 
#*******函數(shù)名作為返回值*********
 
def foo():
    print('foo')
    return bar
 
def bar():
    print('bar')
 
b=foo()
b()

函數(shù)的嵌套及閉包

Python允許創(chuàng)建嵌套函數(shù),通過在函數(shù)內(nèi)部使用def關(guān)鍵字聲明一個函數(shù)作為內(nèi)部函數(shù)

看下面一個例子:

#想執(zhí)行inner函數(shù),兩種方法
def outer():
     x = 1
     def inner():
         print (x) # 1
     # inner() # 2
     return inner
 
# outer()
in_func=outer()
in_func()

作為調(diào)用內(nèi)部函數(shù)inner,有下面兩種方法

in_func=outer() 
in_func()  
###########
inner()(已經(jīng)加載到內(nèi)存啦)

如果直接在外部調(diào)用內(nèi)部函數(shù)inner就會報錯,原因就是這里找不到這個引用變量

但是這里就會有一個問題,在內(nèi)部函數(shù)被調(diào)用執(zhí)行的時候,它的外部函數(shù)也就是outer()已經(jīng)執(zhí)行完畢了,那么為什么inner還是可以調(diào)用聲明在外部outer函數(shù)中的變量x呢?

這里就涉及到我們說的閉包的概念,因為outer里return的inner是一個閉包函數(shù),所以就會有這個x變量

我們也就可以拋出閉包的定義

如果在一個內(nèi)部函數(shù)中,對在外部作用域(且不是在全局作用域)的變量進行引用,那么內(nèi)部函數(shù)就被認為是閉包,內(nèi)部函數(shù)可以稱為閉包函數(shù)

在上面的例子中,inner就是內(nèi)部函數(shù),inner中引用了外部作用域的變量x(x在外部作用域outer,不在模塊的全局作用域)。
所以這個inner函數(shù)就是閉包函數(shù)
如下函數(shù),內(nèi)部閉包函數(shù)的作用是給外部的函數(shù)增加字符串參數(shù)

>>> def saying(something):
...     def outsaying():
...             return "is inner read params %s" %something
...     return outsaying
... 
>>> a = saying('hello')
>>> a()
'is inner read params hello'

上面這個函數(shù)中 outsaying作為內(nèi)部函數(shù)直接訪問外部的變量所以是一個閉包函數(shù),outsaying()函數(shù)可以得到參數(shù)something的值并且記錄下來,return outsaying 這一行返回的是outsaying函數(shù)的一個復制(并沒有直接調(diào)用)

裝飾器概念

裝飾器本質(zhì)上來說是一個函數(shù),該函數(shù)用來處理其他函數(shù),它可以讓其他函數(shù)在不需要修改代碼的前提下增加額外的功能,裝飾器的返回值也會一個函數(shù)對象。裝飾器可以被用于:插入日志、性能測試、事物處理、緩存、權(quán)限校驗等應(yīng)用場景,我們有了裝飾器我們就可以抽離大量與函數(shù)功能本身無關(guān)的雷同代碼并繼續(xù)重用。裝飾器的作用就是為已經(jīng)存在的對象添加額外的功能。

裝飾器定義

裝飾器是一個函數(shù),只要用途是包裝另一個函數(shù)或類。語法上使用特殊符號@表示裝飾器
使用裝飾器時,它們必須出現(xiàn)在函數(shù)或者類定義之前的單獨行上,可以同時使用多個裝飾器
···
@foo
@bar
@spam
def grok(x)
pass
···
上述代碼等同于

def grok(x):
      pass
grok = foo(bar(spam(grok)))

裝飾器也可以接受參數(shù)

@eventhandler('a')
def handle_a(msg):
        pass
@eventhandler('b')
def handle_b(msg):
        pass

如果裝飾器提供參數(shù),裝飾器的語義如下所示:
def handle_button(msg):
tmp = eventhandle('a')# 使用提供的參數(shù)調(diào)用裝飾器
handle_button = tmp(handle_button) # 調(diào)用裝飾器返回的函數(shù)

@trace
def square(x):
      return x*x
等價:
def square(x):
      return x*x
square = trace(square)

現(xiàn)在在生產(chǎn)中有這樣的函數(shù)

def foo():
  print('hello foo')
foo()

現(xiàn)在有一個新的需求,希望可以記錄下函數(shù)的執(zhí)行時間,在這種需求下我們最好不要修改源代碼來實現(xiàn)這個功能,我們可以引入一個內(nèi)置函數(shù),而且為了不影響結(jié)構(gòu),我們還不應(yīng)該改變函數(shù)的調(diào)用方式
這里我們可以引入一個內(nèi)置函數(shù)

import time
 
def show_time(func):
    def wrapper():
        start_time=time.time()
        func()
        end_time=time.time()
        print('spend %s'%(end_time-start_time))
 
    return wrapper
 
 
def foo():
    print('hello foo')
    time.sleep(3)
 
foo=show_time(foo)
foo()

在上面這個例子中函數(shù)show_time就是裝飾器。它將真正的業(yè)務(wù)方法func包裹在函數(shù)里面。

@符號是裝飾器的語法糖,在定義函數(shù)的時候使用,避免多次的賦值操作

import time
 
def show_time(func):
    def wrapper():
        start_time=time.time()
        func()
        end_time=time.time()
        print('spend %s'%(end_time-start_time))
 
    return wrapper
 
@show_time   #foo=show_time(foo)
def foo():
    print('hello foo')
    time.sleep(3)
 
 
@show_time  #bar=show_time(bar)
def bar():
    print('in the bar')
    time.sleep(2)
 
foo()
print('***********')
bar()

這里:foo=show_time(foo)其實就是把內(nèi)置函數(shù)wrapper引用的對象引用給了foo,而wrapper里的變量func之所以可以使用,就是因為wrapper是一個閉包函數(shù)

帶參數(shù)的被裝飾函數(shù)

import time
 
def show_time(func):
 
    def wrapper(a,b):
        start_time=time.time()
        func(a,b)
        end_time=time.time()
        print('spend %s'%(end_time-start_time))
 
    return wrapper
 
@show_time   #add=show_time(add)
def add(a,b):
 
    time.sleep(1)
    print(a+b)
 
add(2,4)
  • 不定長參數(shù)
#***********************************不定長參數(shù)
import time

def show_time(func):

    def wrapper(*args,**kwargs):
        start_time=time.time()
        func(*args,**kwargs)
        end_time=time.time()
        print('spend %s'%(end_time-start_time))

    return wrapper

@show_time   #add=show_time(add)
def add(*args,**kwargs):

    time.sleep(1)
    sum=0
    for i in args:
        sum+=i
    print(sum)

add(2,4,8,9)

裝飾器帶參數(shù)

import time
 
def time_logger(flag=0):
 
    def show_time(func):
 
            def wrapper(*args,**kwargs):
                start_time=time.time()
                func(*args,**kwargs)
                end_time=time.time()
                print('spend %s'%(end_time-start_time))
 
                if flag:
                    print('將這個操作的時間記錄到日志中')
 
            return wrapper
 
    return show_time
 
 
@time_logger(3)
def add(*args,**kwargs):
    time.sleep(1)
    sum=0
    for i in args:
        sum+=i
    print(sum)
 
add(2,7,5)

@time_logger(3) 做了兩件事:

  • (1)time_logger(3):得到閉包函數(shù)show_time,里面保存環(huán)境變量flag
  • (2)@show_time :add=show_time(add)

多層裝飾器

def makebold(fn):
    def wrapper():
        return "<b>" + fn() + "</b>"
    return wrapper
 
def makeitalic(fn):
    def wrapper():
        return "<i>" + fn() + "</i>"
    return wrapper
 
@makebold
@makeitalic
def hello():
    return "hello alvin"
 
hello()

匿名函數(shù): lambda()函數(shù)

Python中,lambda函數(shù)是用一個語句表達的匿名函數(shù),可以用它來代替小的函數(shù)。

# 定義一個函數(shù),參數(shù)接收一個列表,并遍歷
>>> def edit_story(words,func):
...     for word in words:
...             print(func(word))
... 
>>> stairs = ['a','ab','abc','abcd']
>>> def add_str(word):
...     return word+'!!!'
...  
>>> edit_story(stairs,add_str)
a!!!
ab!!!
abc!!!
abcd!!!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 呵呵!作為一名教python的老師,我發(fā)現(xiàn)學生們基本上一開始很難搞定python的裝飾器,也許因為裝飾器確實很難懂...
    TypingQuietly閱讀 20,301評論 26 186
  • 原文出處: dzone 譯文出處:Wu Cheng(@nullRef) 1. 函數(shù) 在python中,函數(shù)通過...
    DraculaWong閱讀 588評論 0 3
  • 無論項目中還是面試都離不開裝飾器話題,裝飾器的強大在于它能夠在不修改原有業(yè)務(wù)邏輯的情況下對代碼進行擴展,權(quán)限校驗、...
    liuzhijun閱讀 2,037評論 0 30
  • 前任對很多很多人來說都是從內(nèi)心深處不愿提及的一類人,那個時候懵懂的愛過, 狠狠地恨過,沒心沒肺地笑過,也傻傻地哭過...
    假裝我是流浪歌手閱讀 336評論 0 1
  • 在社會上,像我這樣平凡的人應(yīng)該是占多的。 畢業(yè)12年多,僥幸混跡于一家國企;至今還是一個基層科員,干著不緊不慢的活...
    winyear閱讀 356評論 0 1

友情鏈接更多精彩內(nèi)容