您好,登錄后才能下訂單哦!
今天小編給大家分享一下Python返回函數、閉包、裝飾器、偏函數怎么使用的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
高階函數除了可以接受函數作為參數外,還可以把函數作為結果值返回。我們在操作函數的時候,如果不需要立刻求和,而是在后面的代碼中,根據需要再計算
例如下面
# -*- coding: utf-8 -*-# python 全棧# author : a wei # 開發時間: 2022/6/23 22:41def sum_fun_a(*args): a = 0 for n in args: a = a + n return a
這是我不需要立即計算我的結果sum_fun方法,不返回求和的結果,而是返回求和的函數,例如下方
# -*- coding: utf-8 -*-# python 全棧# author : a wei # 開發時間: 2022/6/23 22:41def sum_fun_b(*args): def sum_a(): a = 0 for n in args: a = a + n return a return sum_a
當我們調用 sum_fun_b() 時,返回的并不是求和結果,而是求和函數 sum_a , 當我們在調sum_fun_b函數時將他賦值給變量
f1 = sum_fun_b(1, 2, 3, 4, 5)# 此時f為一個對象實例化,并不會直接生成值print(f1()) # 15f2 = sum_fun_b(1, 2, 3, 4, 5)f3 = sum_fun_b(1, 2, 3, 4, 5)print(f2, f3)<function sum_fun_b.<locals>.sum_a at 0x0000016E1E1EFD30> <function sum_fun_b.<locals>.sum_a at 0x0000016E1E1EF700>print(id(f2), id(f3))1899067537152 1899067538880
此時我們直接拿到的值就是15,那可以想一想,此時 f = sum_a,那這里存在一個疑問參數去哪里了?
而且我們看到創建的兩個方法相互不影響的,地址及值是不相同的
在函數 sum_fun_b 中又定義了函數 sum_a ,并且,內部函數 sum_a 可以引用外部函數 sum_fun_b 的參數和局部變量,當 sum_fun_b 返回函數 sum_a 時,而對應的參數和變量都保存在返回的函數中,這里稱為 閉包 。
什么是閉包?
先看一段代碼
# 定義一個函數def fun_a(num_a):# 在函數內部再定義?個函數# 并且這個內部函數?到了外部的變量,這個函數以及?到外部函數的變量及參數叫 閉包 def fun_b(num_b): print('內嵌函數fun_b的參數是:%s,外部函數fun_a的參數是:%s' % (num_b, num_a)) return num_a + num_b # 這里返回的就是閉包的結果 return fun_b# 給fun_a函數賦值,這個10就是傳參給fun_aret = fun_a(10)# 注意這里的10其實是賦值給fun_bprint(ret(10))# 注意這里的90其實是賦值給fun_bprint(ret(90))
運行結果:
內嵌函數fun_b的參數是:10,外部函數fun_a的參數是:1020內嵌函數fun_b的參數是:90,外部函數fun_a的參數是:10100
此時,內部函數對外部函數作?域?變量的引?(?全局變量),則稱內部函數為閉包。
這里閉包需要有三個條件
""" 三個條件,缺一不可: 1)必須有一個內嵌函數(函數里定義的函數)——這對應函數之間的嵌套 2)內嵌函數必須引用一個定義在閉合范圍內(外部函數里)的變量——內部函數引用外部變量 3)外部函數必須返回內嵌函數——必須返回那個內部函數 """
"python# python交互環境編輯器 >>> def counter(start=0): count = [start] def incr(): count[0] += 1 return count[0] return incr >>> c1 = counter(5)>>> print(c1()) 6>>> print(c1()) 7>>> c2=counter(50) >>> print(c2()) 51>>> print(c2()) >52>>>
當一個函數在本地作用域找不到變量申明時會向外層函數尋找,這在函數閉包中很常見但是在本地作用域中使用的變量后,還想對此變量進行更改賦值就會報錯
def test(): count = 1 def add(): print(count) count += 1 return add a = test() a()
報錯信息:
Traceback (most recent call last): ...... UnboundLocalError: local variable 'count' referenced before assignment
如果我在函數內加一行nonlocal count就可解決這個問題
代碼
# -*- coding: UTF-8 -*- # def test(): # count不是局部變量,介于全局變量和局部變量之間的一種變量,nonlocal標識 count = 1 def add(): nonlocal count print(count) count += 1 return count return add a = test() a() # 1 a() # 2
nonlocal聲明的變量不是局部變量,也不是全局變量,而是外部嵌套函數內的變量。
如果從另一個角度來看我們給此函數增加了記錄函數狀態的功能。當然,這也可以通過申明全局變量來實現增加函數狀態的功能。當這樣會出現以下問題:
1. 每次調用函數時,都得在全局作用域申明變量。別人調用函數時還得查看函數內部代碼。 3. 當函數在多個地方被調用并且同時記錄著很多狀態時,會造成非常地混亂。
使用nonlocal的好處是,在為函數添加狀態時不用額外地添加全局變量,因此可以大量地調用此函數并同時記錄著多個函數狀態,每個函數都是獨立、獨特的。針對此項功能其實還個一個方法,就是使用類,通過定義__call__ 可實現在一個實例上直接像函數一樣調用
代碼如下:
def line_conf(a, b): def line(x): return a * x + b return line line1 = line_conf(1, 1) line2 = line_conf(4, 5) print(line1(5)) print(line2(5))
運行結果為
625
從這段代碼中,函數line與變量a,b構成閉包。在創建閉包的時候,我們通過line_conf的參數a,b說明了這兩個變量的取值,這樣,我們就確定了函數的最終形式(y = x + 1和y = 4x + 5)。我們只需要變換參數a,b,就可以獲得不同的
直線表達函數。由此,我們可以看到,閉包也具有提?代碼可復?性的作?。如果沒有閉包,我們需要每次創建函數的時候同時說明a,b,x。這樣,我們就需要更多的參數傳遞,也減少了代碼的可移植性。
1.閉包似優化了變量,原來需要類對象完成的?作,閉包也可以完成 2.由于閉包引?了外部函數的局部變量,則外部函數的局部變量沒有及時釋放,消耗內存
但是還沒有結束,我們知道,函數內部函數,引用外部函數參數或值,進行內部函數運算執行,并不是完全返回一個函數,也有可能是一個在外部函數的值,我們還需要知道返回的函數不會立刻執行,而是直到調用了函數才會執
行。
看代碼:
def fun_a(): fun_list = [] for i in range(1, 4): def fun_b(): return i * i fun_list.append(fun_b) return fun_list f1, f2, f3 = fun_a() print(f1(), f2(), f3())# 結果:9,9,9
這里創建了一個fun_a函數,外部函數的參數fun_list定義了一個列表,在進行遍歷,循環函數fun_b,引用外部變量i 計算返回結果,加入列表,每次循環,都創建了一個新的函數,然后,把創建的3個函數都返回了
但是實際結果并不是我們想要的1,4,9,而是9,9,9,這是為什么呢?
這是因為,返回的函數引用了變量 i ,但不是立刻執行。等到3個函數都返回時,它們所引用的變量i已經變成了3,每一個獨立的函數引用的對象是相同的變量,但是返回的值時候,3個函數都返回時,此時值已經完整了運算,并存儲,當調用函數,產生值不會達成想要的,返回函數不要引用任何循環變量,或者將來會發生變化的變量,但是如果一定需要呢,如何修改這個函數呢?
我們把這里的i賦值給_就可以解決
def test3(): func_list = [] for i in range(1, 4): def test4(i_= i): return i_**2 func_list.append(test4) return func_list f1, f2, f3 = test3()print(f1(), f2(), f3())
可以再創建一個函數,用該函數的參數綁定循環變量當前的值,無論該循環變量后續如何更改,已綁定到函數參數的值不變,那我們就可以完成下面的代碼
# -*- coding: UTF-8 -*- # def fun_a(): def fun_c(i): def fun_b(): return i * i return fun_b fun_list = [] for i in range(1, 4): # f(i)立刻被執行,因此i的當前值被傳入f() fun_list.append(fun_c(i)) return fun_list f1, f2, f3 = fun_a() print(f1(), f2(), f3()) # 1 4 9
什么是裝飾器?
看一段代碼:
# -*- coding: utf-8 -*-# python 全棧# author : a wei # 開發時間: 2022/6/24 17:03def eat(): print('吃飯')def test1(func): def test2(): print('做飯') func() print('洗碗') return test2 eat() # 調用eat函數# 吃飯test1(eat)()# 做飯# 吃飯# 洗碗
由于函數也是一個對象,而且函數對象可以被賦值給變量,所以,通過變量也能調用該函數。也可以將函數賦值變量,做參傳入另一個函數。
""" 裝飾器本質上是一個Python函數,它可以讓其他函數在不需要做任何代碼變動的前提下增加額外功能,裝飾器的返回值 也是一個函數對象。 它經常用于有以下場景,比如:插入日志、性能測試、事務處理、緩存、權限校驗等場景。裝飾器是解決這類問題的絕 佳設計 """
裝飾器的作用就是為已經存在的對象添加額外的功能
先看代碼:
# -*- coding: utf-8 -*-# python 全棧# author : a wei # 開發時間: 2022/6/24 17:03def test1(func): def test2(): print('做飯') func() print('洗碗') return test2@test1 # 裝飾器def eat(): print('吃飯')eat()# 做飯# 吃飯# 洗碗
我們沒有直接將eat函數作為參數傳入test1中,只是將test1函數以@方式裝飾在eat函數上。
也就是說,被裝飾的函數,函數名作為參數,傳入到裝飾器函數上,不影響eat函數的功能,再此基礎上可以根據業務或者功能增加條件或者信息。
(注意:@在裝飾器這里是作為Python語法里面的語法糖寫法,用來做修飾。)
但是我們這里就存在一個問題這里引入魔術方法 name 這是屬于 python 中的內置類屬性,就是它會天生就存在與一個 python 程序中,代表對應程序名稱,一般一段程序作為主線運行程序時其內置名稱就是 main ,當自己作為模塊被調用時就是自己的名字
代碼:
print(eat.__name__)# test2
這并不是我們想要的!輸出應該是" eat"。這里的函數被test2替代了。它重寫了我們函數的名字和注釋文檔,那怎么阻止變化呢,Python提供functools模塊里面的wraps函數解決了問題
代碼:
-*- coding: utf-8 -*- from functools import wrapsdef test1(func): @wraps(func) def test2(): print('做飯') func() print('洗碗') return test2@test1 # 裝飾器def eat(): print('吃飯')eat()# 做飯# 吃飯# 洗碗print(eat.__name__)# eat
我們在裝飾器函數內,作用eat的test2函數上也增加了一個裝飾器wraps還是帶參數的。
這個裝飾器的功能就是不改變使用裝飾器原有函數的結構。
我們熟悉了操作,拿來熟悉一下具體的功能實現,我們可以寫一個打印日志的功能
# -*- coding: utf-8 -*-# python 全棧# author : a wei # 開發時間: 2022/6/24 17:42import timefrom functools import wrapsdef logger(func): @wraps(func) def write_log(): print('[info]--時間:%s' % time.strftime('%Y-%m-%d %H:%M:%S')) func() return write_log@loggerdef work(): print('我在工作')work()# [info]--時間:2022-06-24 17:52:11# 我在工作print(work.__name__)#work
我們也看到裝飾器wraps也是帶參數的,那我們是不是也可以定義帶參數的裝飾器呢,我們可以使用一個函數來包裹裝飾器,調入這個參數。
# -*- coding: utf-8 -*-# python 全棧# author : a wei # 開發時間: 2022/6/24 17:42import timefrom functools import wrapsdef logs(func): @wraps(func) def write_log(*args, **kwargs): print('[info]--時間:%s' % time.strftime('%Y-%m-%d %H:%M:%S')) func(*args, **kwargs) return write_log@logsdef work(): print('我在工作')@logsdef work2(name1, name2): print('%s和%s在工作' % (name1, name2))work2('張三', '李四')# [info]--時間:2022-06-24 18:04:04# 張三和李四在工作
把日志寫入文件
# -*- coding: utf-8 -*-# python 全棧# author : a wei# 開發時間: 2022/6/20 0:06import timefrom functools import wrapsdef logger(file): def logs(fun): @wraps(fun) def write_log(*args, **kwargs): log = '[info] 時間是:%s' % time.strftime('%Y-%m-%d %H:%M:%S') print(log) with open(file, 'a+') as f: f.write(log) fun(*args, **kwargs) return write_log return logs@logger('work.log') # 使用裝飾器來給 work函數增加記錄日志的功能def work(name, name2): # 1.當前 work可能有多個參數 2.自定義日志文件的名字和位置,記錄日志級別 print(f'{name}和{name2}在工作')work('張三', '李四')
終端輸出:
這里生成里work.log日志文件
里面記錄日志
這里我們將帶參數的帶入進去根據代碼流程執行生成了文件并將文件打印進去現在我們有了能用于正式環境的logs裝飾器,但當我們的應用的某些部分還比較脆弱時,異常也許是需要更緊急關注的事情。
比方說有時你只想打日志到一個文件。而有時你想把引起你注意的問題發送到一個email,同時也保留
日志,留個記錄。
這是一個使用繼承的場景,但目前為止我們只看到過用來構建裝飾器的函數。
# -*- coding: utf-8 -*-# python 全棧# author : a wei# 開發時間: 2022/6/20 0:06import timefrom functools import wraps# 不使用函數做裝飾器,使用類做裝飾器class Logs(object): def __init__(self, log_file='out.log', level='info'): # 初始化一個默認文件和默認日志級別 self.log_file = log_file self.level = level def __call__(self, fun): # 定義裝飾器,需要一個接受函數 @wraps(fun) def write_log(name, name2): log = '[%s] 時間是:%s' % (self.level, time.strftime('%Y-%m-%d %H:%M:%S')) print(log) with open(self.log_file, 'a+') as f: f.write(log) fun(name, name2) return write_log@Logs() # 使用裝飾器來給 work函數增加記錄日志的功能def work(name, name2): # 1.當前 work可能有多個參數 2.自定義日志文件的名字和位置,記錄日志級別 print(f'{name}和{name2}在工作')work('張三', '李四') # 調用work函數
這個實現有一個優勢,在于比嵌套函數的方式更加整潔,而且包裹一個函數還是使用跟以前一樣的語法
Python的 functools 模塊提供了很多有用的功能,其中一個就是偏函(Partial function)。要注意,這里的偏函數和數學意義上的偏函數不一樣。
在介紹函數參數的時候,我們講到,通過設定參數的默認值,可以降低函數調用的難度。而偏函數也可以做到這一點。
例如:int() 函數可以把字符串轉換為整數,當僅傳入字符串時, int() 函數默認按十進制轉換
>>> int('123') 123
但 int() 函數還提供額外的 base 參數,默認值為 10 。如果傳入 base 參數,就可以做進制的轉換
>>> int('12345', base=8) 5349 >>> int('12345', 16) 74565
如果要轉換大量的二進制字符串,每次都傳入 int(x, base=2) 非常麻煩,于是,我們想到,可以定義一個int2() 的函數,默認把 base=2 傳進去:
代碼:
# 定一個轉換義函數 >>> def int_1(num, base=2): return int(num, base) >>> int_1('1000000') 64>>> int_1('1010101') 85
把一個函數的某些參數給固定住(也就是設置默認值),返回一個新的函數,調用這個新函數會更簡單
繼續優化,functools.partial 就是幫助我們創建一個偏函數的,不需要我們自己定義 int_1() ,可以直接使用下面的代碼創 建一個新的函數 int_1
# 導入 >>> import functools # 偏函數處理 >>> int_2 = functools.partial(int, base=2) >>> int_2('1000000') 64>>> int_2('1010101') 85
理清了 functools.partial 的作用就是,把一個函數的某些參數給固定住(也就是設置默認值),返回一個新的函數,調用這個新函數會更簡單。
注意到上面的新的 int_2 函數,僅僅是把 base 參數重新設定默認值為 2 ,但也可以在函數調用時傳入其他值實際上固定了int()函數的關鍵字參數 base
int2('10010')
相當于是:
kw = { base: 2 } int('10010', **kw)
當函數的參數個數太多,需要簡化時,使用 functools.partial 可以創建一個新的函數,這個新函數可以固定住原函數的部分參數,從而在調用時更簡單
以上就是“Python返回函數、閉包、裝飾器、偏函數怎么使用”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。