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