程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

python 閉包與裝飾器

編輯:Python

參考:

 Python 函數裝飾器 | 菜鳥教程

一、什麼是閉包

python中的閉包從表現形式上定義(解釋)為:

如果在一個內部函數裡,對在外部作用域(但不是在全局作用域)的變量進行引用,那麼內部函數就被認為是閉包(closure).

python中函數名是一個特殊的變量,它可以作為另一個函數的返回值,而閉包就是一個函數返回另一個函數後,其內部的局部變量還被另一個函數引用。

閉包的作用就是讓一個變量能夠常駐內存。

def func(name): # 定義外層函數
def inner_func(age): # 內層函數
print('name: ', name, ', age: ', age)
return inner_func # 注意此處要返回,才能體現閉包
if __name__ == '__main__':
bb = func('jayson') # 將字符串傳給func函數,返回inner_func並賦值給變量
bb(28) # 通過變量調用func函數,傳入參數,從而完成閉包

打印結果:

name: jayson , age: 28

閉包的用途

兩個用處:① 可以讀取函數內部的變量,②讓這些變量的值始終保持在內存中。

②讓函數內部的局部變量始終保持在內存中:

怎麼來理解這句話呢?一般來說,函數內部的局部變量在這個函數運行完以後,就會被Python的垃圾回收機制從內存中清除掉。如果我們希望這個局部變量能夠長久的保存在內存中,那麼就可以用閉包來實現這個功能。

這裡借用
@千山飛雪
的例子(來自於:千山飛雪:深入淺出python閉包)。請看下面的代碼。

以一個類似棋盤游戲的例子來說明。假設棋盤大小為50*50,左上角為坐標系原點(0,0),我需要一個函數,接收2個參數,分別為方向(direction),步長(step),該函數控制棋子的運動。 這裡需要說明的是,每次運動的起點都是上次運動結束的終點。

def create(pos=[0,0]):
def go(direction, step):
new_x = pos[0]+direction[0]*step
new_y = pos[1]+direction[1]*step
pos[0] = new_x
pos[1] = new_y
return pos
return go
player = create()
print(player([1,0],10))
print(player([0,1],20))
print(player([-1,0],10))

在這段代碼中,player實際上就是閉包go函數的一個實例對象。

它一共運行了三次,第一次是沿X軸前進了10來到[10,0],第二次是沿Y軸前進了20來到 [10, 20],,第三次是反方向沿X軸退了10來到[0, 20]。

這證明了,函數create中的局部變量pos一直保存在內存中,並沒有在create調用後被自動清除。

為什麼會這樣呢?原因就在於create是go的父函數,而go被賦給了一個全局變量,這導致go始終在內存中,而go的存在依賴於create,因此create也始終在內存中,不會在調用結束後,被垃圾回收機制(garbage collection)回收。

這個時候,閉包使得函數的實例對象的內部變量,變得很像一個類的實例對象的屬性,可以一直保存在內存中,並不斷的對其進行運算。

二、裝飾器(decorator)

裝飾器就是為了不修改原函數的定義,並使原函數在運行時動態增加功能的方式,一般來說裝飾器是一個返回函數的高階函數。

簡單地說:他們是修改其他函數的功能的函數。

裝飾器讓你在一個函數的前後去執行代碼。

def hi(name="yasoob"):
def greet():
return "now you are in the greet() function"
def welcome():
return "now you are in the welcome() function"
if name == "yasoob":
return greet
else:
return welcome
a = hi()
print(a)
#outputs: <function greet at 0x7f2143c01500>
#上面清晰地展示了`a`現在指向到hi()函數中的greet()函數
#現在試試這個
print(a())
#outputs: now you are in the greet() function

在 if/else 語句中我們返回 greet 和 welcome,而不是 greet() 和 welcome()。

當你把一對小括號放在函數後面,這個函數就會執行;然而如果你不放括號在它後面,那它可以被到處傳遞,並且可以賦值給別的變量而不去執行它。

藍本規范:

from functools import wraps
def decorator_name(f):
@wraps(f)
def decorated(*args, **kwargs):
if not can_run:
return "Function will not run"
return f(*args, **kwargs)
return decorated
@decorator_name
def func():
return("Function is running")
can_run = True
print(func())
# Output: Function is running
can_run = False
print(func())
# Output: Function will not run

1、將被裝飾的函數,作為一個變量,傳入了裝飾函數裡

2、裝飾函數,只有一個傳參,那就是被裝飾的函數

3、開始執行最外層return的這個函數,裡面的變量就是被裝飾的函數(傳進來的函數) 

其實有用的代碼就這一塊,其他亂七八糟的都要忽略掉,裡面的那個變量,就是被傳入的函數

使用場景

授權(Authorization)

裝飾器能有助於檢查某個人是否被授權去使用一個web應用的端點(endpoint)。它們被大量使用於Flask和Django web框架中。這裡是一個例子來使用基於裝飾器的授權:

from functools import wraps
def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
authenticate()
return f(*args, **kwargs)
return decorated

有用的就這一小塊

日志(Logging)

日志是裝飾器運用的另一個亮點。這是個例子:

from functools import wraps
def logit(func):
@wraps(func)
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
@logit
def addition_func(x):
"""Do some math."""
return x + x
result = addition_func(4)
# Output: addition_func was called

有用的就這一小塊

帶參數的裝飾器

在函數中嵌入裝飾器

我們回到日志的例子,並創建一個包裹函數,能讓我們指定一個用於輸出的日志文件。

from functools import wraps
def logit(logfile='out.log'):
def logging_decorator(func):
@wraps(func)
def wrapped_function(*args, **kwargs):
log_string = func.__name__ + " was called"
print(log_string)
# 打開logfile,並寫入內容
with open(logfile, 'a') as opened_file:
# 現在將日志打到指定的logfile
opened_file.write(log_string + '\n')
return func(*args, **kwargs)
return wrapped_function
return logging_decorator
@logit()
def myfunc1():
pass
myfunc1()
# Output: myfunc1 was called
# 現在一個叫做 out.log 的文件出現了,裡面的內容就是上面的字符串
@logit(logfile='func2.log')
def myfunc2():
pass
myfunc2()
# Output: myfunc2 was called
# 現在一個叫做 func2.log 的文件出現了,裡面的內容就是上面的字符串

1、有用的只有這一塊

 2、多包裹一層就是為了多傳遞一個參數


  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved