看到各位大佬們都會(huì)把原始數(shù)據(jù)進(jìn)行歸一化,再處理??墒嵌紱](méi)有人講怎樣把歸一化的數(shù)據(jù)還原回來(lái)。
在牟定等地區(qū),都構(gòu)建了全面的區(qū)域性戰(zhàn)略布局,加強(qiáng)發(fā)展的系統(tǒng)性、市場(chǎng)前瞻性、產(chǎn)品創(chuàng)新能力,以專注、極致的服務(wù)理念,為客戶提供成都做網(wǎng)站、成都網(wǎng)站建設(shè) 網(wǎng)站設(shè)計(jì)制作定制網(wǎng)站制作,公司網(wǎng)站建設(shè),企業(yè)網(wǎng)站建設(shè),品牌網(wǎng)站設(shè)計(jì),成都營(yíng)銷網(wǎng)站建設(shè),外貿(mào)網(wǎng)站建設(shè),牟定網(wǎng)站建設(shè)費(fèi)用合理。
目前可找到的方法就只有matlab上的這個(gè)函數(shù):
xtt = mapminmax('reverse',y1,ps)
在python上,就看到許多人推薦用sklearn進(jìn)行歸一化,但沒(méi)有還原的方法呀。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
你要問(wèn)我為什么 要還原?
把日期和氣溫的數(shù)據(jù)放到模型里跑半天,想看看下一天的氣溫,結(jié)果出來(lái)一個(gè)0.837之類東西。
sklearn中transform用來(lái)歸一化后,可以用inverse_transform還原。
前言
Python 一直以來(lái)被大家所詬病的一點(diǎn)就是執(zhí)行速度慢,但不可否認(rèn)的是 Python 依然是我們學(xué)習(xí)和工作中的一大利器。本文總結(jié)了15個(gè)tips有助于提升 Python 執(zhí)行速度、優(yōu)化性能。
關(guān)于 Python 如何精確地測(cè)量程序的執(zhí)行時(shí)間,這個(gè)問(wèn)題看起來(lái)簡(jiǎn)單其實(shí)很復(fù)雜,因?yàn)槌绦虻膱?zhí)行時(shí)間受到很多因素的影響,例如操作系統(tǒng)、Python 版本以及相關(guān)硬件(CPU 性能、內(nèi)存讀寫(xiě)速度)等。在同一臺(tái)電腦上運(yùn)行相同版本的語(yǔ)言時(shí),上述因素就是確定的了,但是程序的睡眠時(shí)間依然是變化的,且電腦上正在運(yùn)行的其他程序也會(huì)對(duì)實(shí)驗(yàn)有干擾,因此嚴(yán)格來(lái)說(shuō)這就是實(shí)驗(yàn)不可重復(fù)。
我了解到的關(guān)于計(jì)時(shí)比較有代表性的兩個(gè)庫(kù)就是 time 和 timeit 。
其中, time 庫(kù)中有 time() 、 perf_counter() 以及 process_time() 三個(gè)函數(shù)可用來(lái)計(jì)時(shí)(以秒為單位),加后綴 _ns 表示以納秒計(jì)時(shí)(自 Python3.7 始)。在此之前還有 clock() 函數(shù),但是在 Python3.3 之后被移除了。上述三者的區(qū)別如下:
與 time 庫(kù)相比, timeit 有兩個(gè)優(yōu)點(diǎn):
timeit.timeit(stmt='pass', setup='pass', timer= , number=1000000, globals=None) 參數(shù)說(shuō)明:
本文所有的計(jì)時(shí)均采用 timeit 方法,且采用默認(rèn)的執(zhí)行次數(shù)一百萬(wàn)次。
為什么要執(zhí)行一百萬(wàn)次呢?因?yàn)槲覀兊臏y(cè)試程序很短,如果不執(zhí)行這么多次的話,根本看不出差距。
Exp1:將字符串?dāng)?shù)組中的小寫(xiě)字母轉(zhuǎn)為大寫(xiě)字母。
測(cè)試數(shù)組為 oldlist = ['life', 'is', 'short', 'i', 'choose', 'python']。
方法一
方法二
方法一耗時(shí) 0.5267724000000005s ,方法二耗時(shí) 0.41462569999999843s ,性能提升 21.29%
Exp2:求兩個(gè) list 的交集。
測(cè)試數(shù)組:a = [1,2,3,4,5],b = [2,4,6,8,10]。
方法一
方法二
方法一耗時(shí) 0.9507264000000006s ,方法二耗時(shí) 0.6148200999999993s ,性能提升 35.33%
關(guān)于 set() 的語(yǔ)法: | 、 、 - 分別表示求并集、交集、差集。
我們可以通過(guò)多種方式對(duì)序列進(jìn)行排序,但其實(shí)自己編寫(xiě)排序算法的方法有些得不償失。因?yàn)閮?nèi)置的 sort() 或 sorted() 方法已經(jīng)足夠優(yōu)秀了,且利用參數(shù) key 可以實(shí)現(xiàn)不同的功能,非常靈活。二者的區(qū)別是 sort() 方法僅被定義在 list 中,而 sorted() 是全局方法對(duì)所有的可迭代序列都有效。
Exp3:分別使用快排和 sort() 方法對(duì)同一列表排序。
測(cè)試數(shù)組:lists = [2,1,4,3,0]。
方法一
方法二
方法一耗時(shí) 2.4796975000000003s ,方法二耗時(shí) 0.05551999999999424s ,性能提升 97.76%
順帶一提, sorted() 方法耗時(shí) 0.1339823999987857s 。
可以看出, sort() 作為 list 專屬的排序方法還是很強(qiáng)的, sorted() 雖然比前者慢一點(diǎn),但是勝在它“不挑食”,它對(duì)所有的可迭代序列都有效。
擴(kuò)展 :如何定義 sort() 或 sorted() 方法的 key
1.通過(guò) lambda 定義
2.通過(guò) operator 定義
operator 的 itemgetter() 適用于普通數(shù)組排序, attrgetter() 適用于對(duì)象數(shù)組排序
3.通過(guò) cmp_to_key() 定義,最為靈活
Exp4:統(tǒng)計(jì)字符串中每個(gè)字符出現(xiàn)的次數(shù)。
測(cè)試數(shù)組:sentence='life is short, i choose python'。
方法一
方法二
方法一耗時(shí) 2.8105250000000055s ,方法二耗時(shí) 1.6317423000000062s ,性能提升 41.94%
列表推導(dǎo)(list comprehension)短小精悍。在小代碼片段中,可能沒(méi)有太大的區(qū)別。但是在大型開(kāi)發(fā)中,它可以節(jié)省一些時(shí)間。
Exp5:對(duì)列表中的奇數(shù)求平方,偶數(shù)不變。
測(cè)試數(shù)組:oldlist = range(10)。
方法一
方法二
方法一耗時(shí) 1.5342976000000021s ,方法二耗時(shí) 1.4181957999999923s ,性能提升 7.57%
大多數(shù)人都習(xí)慣使用 + 來(lái)連接字符串。但其實(shí),這種方法非常低效。因?yàn)椋? + 操作在每一步中都會(huì)創(chuàng)建一個(gè)新字符串并復(fù)制舊字符串。更好的方法是用 join() 來(lái)連接字符串。關(guān)于字符串的其他操作,也盡量使用內(nèi)置函數(shù),如 isalpha() 、 isdigit() 、 startswith() 、 endswith() 等。
Exp6:將字符串列表中的元素連接起來(lái)。
測(cè)試數(shù)組:oldlist = ['life', 'is', 'short', 'i', 'choose', 'python']。
方法一
方法二
方法一耗時(shí) 0.27489080000000854s ,方法二耗時(shí) 0.08166570000000206s ,性能提升 70.29%
join 還有一個(gè)非常舒服的點(diǎn),就是它可以指定連接的分隔符,舉個(gè)例子
life//is//short//i//choose//python
Exp6:交換x,y的值。
測(cè)試數(shù)據(jù):x, y = 100, 200。
方法一
方法二
方法一耗時(shí) 0.027853900000010867s ,方法二耗時(shí) 0.02398730000000171s ,性能提升 13.88%
在不知道確切的循環(huán)次數(shù)時(shí),常規(guī)方法是使用 while True 進(jìn)行無(wú)限循環(huán),在代碼塊中判斷是否滿足循環(huán)終止條件。雖然這樣做沒(méi)有任何問(wèn)題,但 while 1 的執(zhí)行速度比 while True 更快。因?yàn)樗且环N數(shù)值轉(zhuǎn)換,可以更快地生成輸出。
Exp8:分別用 while 1 和 while True 循環(huán) 100 次。
方法一
方法二
方法一耗時(shí) 3.679268300000004s ,方法二耗時(shí) 3.607847499999991s ,性能提升 1.94%
將文件存儲(chǔ)在高速緩存中有助于快速恢復(fù)功能。Python 支持裝飾器緩存,該緩存在內(nèi)存中維護(hù)特定類型的緩存,以實(shí)現(xiàn)最佳軟件驅(qū)動(dòng)速度。我們使用 lru_cache 裝飾器來(lái)為斐波那契函數(shù)提供緩存功能,在使用 fibonacci 遞歸函數(shù)時(shí),存在大量的重復(fù)計(jì)算,例如 fibonacci(1) 、 fibonacci(2) 就運(yùn)行了很多次。而在使用了 lru_cache 后,所有的重復(fù)計(jì)算只會(huì)執(zhí)行一次,從而大大提高程序的執(zhí)行效率。
Exp9:求斐波那契數(shù)列。
測(cè)試數(shù)據(jù):fibonacci(7)。
方法一
方法二
方法一耗時(shí) 3.955014900000009s ,方法二耗時(shí) 0.05077979999998661s ,性能提升 98.72%
注意事項(xiàng):
我被執(zhí)行了(執(zhí)行了兩次 demo(1, 2) ,卻只輸出一次)
functools.lru_cache(maxsize=128, typed=False) 的兩個(gè)可選參數(shù):
點(diǎn)運(yùn)算符( . )用來(lái)訪問(wèn)對(duì)象的屬性或方法,這會(huì)引起程序使用 __getattribute__() 和 __getattr__() 進(jìn)行字典查找,從而帶來(lái)不必要的開(kāi)銷。尤其注意,在循環(huán)當(dāng)中,更要減少點(diǎn)運(yùn)算符的使用,應(yīng)該將它移到循環(huán)外處理。
這啟發(fā)我們應(yīng)該盡量使用 from ... import ... 這種方式來(lái)導(dǎo)包,而不是在需要使用某方法時(shí)通過(guò)點(diǎn)運(yùn)算符來(lái)獲取。其實(shí)不光是點(diǎn)運(yùn)算符,其他很多不必要的運(yùn)算我們都盡量移到循環(huán)外處理。
Exp10:將字符串?dāng)?shù)組中的小寫(xiě)字母轉(zhuǎn)為大寫(xiě)字母。
測(cè)試數(shù)組為 oldlist = ['life', 'is', 'short', 'i', 'choose', 'python']。
方法一
方法二
方法一耗時(shí) 0.7235491999999795s ,方法二耗時(shí) 0.5475435999999831s ,性能提升 24.33%
當(dāng)我們知道具體要循環(huán)多少次時(shí),使用 for 循環(huán)比使用 while 循環(huán)更好。
Exp12:使用 for 和 while 分別循環(huán) 100 次。
方法一
方法二
方法一耗時(shí) 3.894683299999997s ,方法二耗時(shí) 1.0198077999999953s ,性能提升 73.82%
Numba 可以將 Python 函數(shù)編譯碼為機(jī)器碼執(zhí)行,大大提高代碼執(zhí)行速度,甚至可以接近 C 或 FORTRAN 的速度。它能和 Numpy 配合使用,在 for 循環(huán)中或存在大量計(jì)算時(shí)能顯著地提高執(zhí)行效率。
Exp12:求從 1 加到 100 的和。
方法一
方法二
方法一耗時(shí) 3.7199997000000167s ,方法二耗時(shí) 0.23769430000001535s ,性能提升 93.61%
矢量化是 NumPy 中的一種強(qiáng)大功能,可以將操作表達(dá)為在整個(gè)數(shù)組上而不是在各個(gè)元素上發(fā)生。這種用數(shù)組表達(dá)式替換顯式循環(huán)的做法通常稱為矢量化。
在 Python 中循環(huán)數(shù)組或任何數(shù)據(jù)結(jié)構(gòu)時(shí),會(huì)涉及很多開(kāi)銷。NumPy 中的向量化操作將內(nèi)部循環(huán)委托給高度優(yōu)化的 C 和 Fortran 函數(shù),從而使 Python 代碼更加快速。
Exp13:兩個(gè)長(zhǎng)度相同的序列逐元素相乘。
測(cè)試數(shù)組:a = [1,2,3,4,5], b = [2,4,6,8,10]
方法一
方法二
方法一耗時(shí) 0.6706845000000214s ,方法二耗時(shí) 0.3070132000000001s ,性能提升 54.22%
若要檢查列表中是否包含某成員,通常使用 in 關(guān)鍵字更快。
Exp14:檢查列表中是否包含某成員。
測(cè)試數(shù)組:lists = ['life', 'is', 'short', 'i', 'choose', 'python']
方法一
方法二
方法一耗時(shí) 0.16038449999999216s ,方法二耗時(shí) 0.04139250000000061s ,性能提升 74.19%
itertools 是用來(lái)操作迭代器的一個(gè)模塊,其函數(shù)主要可以分為三類:無(wú)限迭代器、有限迭代器、組合迭代器。
Exp15:返回列表的全排列。
測(cè)試數(shù)組:["Alice", "Bob", "Carol"]
方法一
方法二
方法一耗時(shí) 3.867292899999484s ,方法二耗時(shí) 0.3875405000007959s ,性能提升 89.98%
根據(jù)上面的測(cè)試數(shù)據(jù),我繪制了下面這張實(shí)驗(yàn)結(jié)果圖,可以更加直觀的看出不同方法帶來(lái)的性能差異。
從圖中可以看出,大部分的技巧所帶來(lái)的性能增幅還是比較可觀的,但也有少部分技巧的增幅較?。ɡ缇幪?hào)5、7、8,其中,第 8 條的兩種方法幾乎沒(méi)有差異)。
總結(jié)下來(lái),我覺(jué)得其實(shí)就是下面這兩條原則:
內(nèi)置庫(kù)函數(shù)由專業(yè)的開(kāi)發(fā)人員編寫(xiě)并經(jīng)過(guò)了多次測(cè)試,很多庫(kù)函數(shù)的底層是用 C 語(yǔ)言開(kāi)發(fā)的。因此,這些函數(shù)總體來(lái)說(shuō)是非常高效的(比如 sort() 、 join() 等),自己編寫(xiě)的方法很難超越它們,還不如省省功夫,不要重復(fù)造輪子了,何況你造的輪子可能更差。所以,如果函數(shù)庫(kù)中已經(jīng)存在該函數(shù),就直接拿來(lái)用。
有很多優(yōu)秀的第三方庫(kù),它們的底層可能是用 C 和 Fortran 來(lái)實(shí)現(xiàn)的,像這樣的庫(kù)用起來(lái)絕對(duì)不會(huì)吃虧,比如前文提到的 Numpy 和 Numba,它們帶來(lái)的提升都是非常驚人的。類似這樣的庫(kù)還有很多,比如Cython、PyPy等,這里我只是拋磚引玉。
原文鏈接:
my_df是一個(gè)二維的結(jié)構(gòu),如下圖所示:
stack函數(shù)會(huì)把列索引轉(zhuǎn)成行索引,即把列索引換成二級(jí)的行索引,即所有列壓縮到一列。請(qǐng)注意,這個(gè)的空值會(huì)跳過(guò),下圖中的數(shù)據(jù)結(jié)構(gòu)只有7個(gè)元素,7行1列。
a['b','two']
a['d','two']
#上面兩個(gè)索引是可以的,下面的是會(huì)報(bào)錯(cuò)KeyError
a['d','one']
unstack函數(shù)會(huì)把行索引轉(zhuǎn)成列索引,即把行索引換成二級(jí)的列索引,即所有行壓縮到一行。注:實(shí)際上,Python似乎不分行向量或列向量,如果對(duì)一個(gè)Series對(duì)象進(jìn)行轉(zhuǎn)置,則還是其本身。根據(jù)shape函數(shù)的返回可知,這是一個(gè)8行1列的數(shù)據(jù)結(jié)構(gòu)。
stack函數(shù)會(huì)把二級(jí)的行索引轉(zhuǎn)成列索引,恢復(fù)其二維結(jié)構(gòu)。
我們看到,用過(guò)stack函數(shù)后可以用unstack函數(shù)來(lái)恢復(fù)其原貌。反過(guò)來(lái)如何呢?
會(huì)報(bào)錯(cuò):AttributeError: 'Series' object has no attribute 'stack'
我們發(fā)現(xiàn),stack和unstack兩個(gè)函數(shù)的組合,如下所示,只有前兩個(gè)是可以的沒(méi)有問(wèn)題的。這是因?yàn)橐粋€(gè)二維數(shù)據(jù)經(jīng)過(guò)stack或unstack后,變成了一個(gè)Series結(jié)構(gòu),而Series有unstack沒(méi)有stack。
my_df.stack().unstack()
my_df.unstack().unstack()
#my_df.stack().stack()
#my_df.unstack().stack()
a=my_df.stack()
print(type(a))
print(dir(a))
如下圖所示,用過(guò)了unstack后,怎么恢復(fù)原狀呢?轉(zhuǎn)置一下即可。
selenium是個(gè)模塊,直接導(dǎo)入selenium中的webdriver就可以了,第一行直接去掉。
“在Python中,函數(shù)本身也是對(duì)象”
這一本質(zhì)。那不妨慢慢來(lái),從最基本的概念開(kāi)始,討論一下這個(gè)問(wèn)題:
1. Python中一切皆對(duì)象
這恐怕是學(xué)習(xí)Python最有用的一句話。想必你已經(jīng)知道Python中的list, tuple, dict等內(nèi)置數(shù)據(jù)結(jié)構(gòu),當(dāng)你執(zhí)行:
alist = [1, 2, 3]
時(shí),你就創(chuàng)建了一個(gè)列表對(duì)象,并且用alist這個(gè)變量引用它:
當(dāng)然你也可以自己定義一個(gè)類:
class House(object):
def __init__(self, area, city):
self.area = area
self.city = city
def sell(self, price):
[...] #other code
return price
然后創(chuàng)建一個(gè)類的對(duì)象:
house = House(200, 'Shanghai')
OK,你立馬就在上海有了一套200平米的房子,它有一些屬性(area, city),和一些方法(__init__, self):
2. 函數(shù)是第一類對(duì)象
和list, tuple, dict以及用House創(chuàng)建的對(duì)象一樣,當(dāng)你定義一個(gè)函數(shù)時(shí),函數(shù)也是對(duì)象:
def func(a, b):
return a+b
在全局域,函數(shù)對(duì)象被函數(shù)名引用著,它接收兩個(gè)參數(shù)a和b,計(jì)算這兩個(gè)參數(shù)的和作為返回值。
所謂第一類對(duì)象,意思是可以用標(biāo)識(shí)符給對(duì)象命名,并且對(duì)象可以被當(dāng)作數(shù)據(jù)處理,例如賦值、作為參數(shù)傳遞給函數(shù),或者作為返回值return 等
因此,你完全可以用其他變量名引用這個(gè)函數(shù)對(duì)象:
add = func
這樣,你就可以像調(diào)用func(1, 2)一樣,通過(guò)新的引用調(diào)用函數(shù)了:
print func(1, 2)
print add(1, 2) #the same as func(1, 2)
或者將函數(shù)對(duì)象作為參數(shù),傳遞給另一個(gè)函數(shù):
def caller_func(f):
return f(1, 2)
if __name__ == "__main__":
print caller_func(func)
可以看到,
函數(shù)對(duì)象func作為參數(shù)傳遞給caller_func函數(shù),傳參過(guò)程類似于一個(gè)賦值操作f=func;
于是func函數(shù)對(duì)象,被caller_func函數(shù)作用域中的局部變量f引用,f實(shí)際指向了函數(shù)func;cc
當(dāng)執(zhí)行return f(1, 2)的時(shí)候,相當(dāng)于執(zhí)行了return func(1, 2);
因此輸出結(jié)果為3。
3. 函數(shù)對(duì)象 vs 函數(shù)調(diào)用
無(wú)論是把函數(shù)賦值給新的標(biāo)識(shí)符,還是作為參數(shù)傳遞給新的函數(shù),針對(duì)的都是函數(shù)對(duì)象本身,而不是函數(shù)的調(diào)用。
用一個(gè)更加簡(jiǎn)單,但從外觀上看,更容易產(chǎn)生混淆的例子來(lái)說(shuō)明這個(gè)問(wèn)題。例如定義了下面這個(gè)函數(shù):
def func():
return "hello,world"
然后分別執(zhí)行兩次賦值:
ref1 = func #將函數(shù)對(duì)象賦值給ref1
ref2 = func() #調(diào)用函數(shù),將函數(shù)的返回值("hello,world"字符串)賦值給ref2
很多初學(xué)者會(huì)混淆這兩種賦值,通過(guò)Python內(nèi)建的type函數(shù),可以查看一下這兩次賦值的結(jié)果:
In [4]: type(ref1)
Out[4]: function
In [5]: type(ref2)
Out[5]: str
可以看到,ref1引用了函數(shù)對(duì)象本身,而ref2則引用了函數(shù)的返回值。通過(guò)內(nèi)建的callable函數(shù),可以進(jìn)一步驗(yàn)證ref1是可調(diào)用的,而ref2是不可調(diào)用的:
In [9]: callable(ref1)
Out[9]: True
In [10]: callable(ref2)
Out[10]: False
傳參的效果與之類似。
4. 閉包LEGB法則
所謂閉包,就是將組成函數(shù)的語(yǔ)句和這些語(yǔ)句的執(zhí)行環(huán)境打包在一起時(shí),得到的對(duì)象
聽(tīng)上去的確有些復(fù)雜,還是用一個(gè)栗子來(lái)幫助理解一下。假設(shè)我們?cè)趂oo.py模塊中做了如下定義:
#foo.py
filename = "foo.py"
def call_func(f):
return f() #如前面介紹的,f引用一個(gè)函數(shù)對(duì)象,然后調(diào)用它
在另一個(gè)func.py模塊中,寫(xiě)下了這樣的代碼:
#func.py
import foo #導(dǎo)入foo.py
filename = "func.py"
def show_filename():
return "filename: %s" % filename
if __name__ == "__main__":
print foo.call_func(show_filename) #注意:實(shí)際發(fā)生調(diào)用的位置,是在foo.call_func函數(shù)中
當(dāng)我們用python func.py命令執(zhí)行func.py時(shí)輸出結(jié)果為:
chiyu@chiyu-PC:~$ python func.py
filename:func.py
很顯然show_filename()函數(shù)使用的filename變量的值,是在與它相同環(huán)境(func.py模塊)中定義的那個(gè)。盡管foo.py模塊中也定義了同名的filename變量,而且實(shí)際調(diào)用show_filename的位置也是在foo.py的call_func內(nèi)部。
而對(duì)于嵌套函數(shù),這一機(jī)制則會(huì)表現(xiàn)的更加明顯:閉包將會(huì)捕捉內(nèi)層函數(shù)執(zhí)行所需的整個(gè)環(huán)境:
#enclosed.py
import foo
def wrapper():
filename = "enclosed.py"
def show_filename():
return "filename: %s" % filename
print foo.call_func(show_filename) #輸出:filename: enclosed.py
實(shí)際上,每一個(gè)函數(shù)對(duì)象,都有一個(gè)指向了該函數(shù)定義時(shí)所在全局名稱空間的__globals__屬性:
#show_filename inside wrapper
#show_filename.__globals__
{
'__builtins__': module '__builtin__' (built-in), #內(nèi)建作用域環(huán)境
'__file__': 'enclosed.py',
'wrapper': function wrapper at 0x7f84768b6578, #直接外圍環(huán)境
'__package__': None,
'__name__': '__main__',
'foo': module 'foo' from '/home/chiyu/foo.pyc', #全局環(huán)境
'__doc__': None
}
當(dāng)代碼執(zhí)行到show_filename中的return "filename: %s" % filename語(yǔ)句時(shí),解析器按照下面的順序查找filename變量:
Local - 本地函數(shù)(show_filename)內(nèi)部,通過(guò)任何方式賦值的,而且沒(méi)有被global關(guān)鍵字聲明為全局變量的filename變量;
Enclosing - 直接外圍空間(上層函數(shù)wrapper)的本地作用域,查找filename變量(如果有多層嵌套,則由內(nèi)而外逐層查找,直至最外層的函數(shù));
Global - 全局空間(模塊enclosed.py),在模塊頂層賦值的filename變量;
Builtin - 內(nèi)置模塊(__builtin__)中預(yù)定義的變量名中查找filename變量;
在任何一層先找到了符合要求的filename變量,則不再向更外層查找。如果直到Builtin層仍然沒(méi)有找到符合要求的變量,則拋出NameError異常。這就是變量名解析的:LEGB法則。
總結(jié):
閉包最重要的使用價(jià)值在于:封存函數(shù)執(zhí)行的上下文環(huán)境;
閉包在其捕捉的執(zhí)行環(huán)境(def語(yǔ)句塊所在上下文)中,也遵循LEGB規(guī)則逐層查找,直至找到符合要求的變量,或者拋出異常。
5. 裝飾器語(yǔ)法糖(syntax sugar)
那么閉包和裝飾器又有什么關(guān)系呢?
上文提到閉包的重要特性:封存上下文,這一特性可以巧妙的被用于現(xiàn)有函數(shù)的包裝,從而為現(xiàn)有函數(shù)更加功能。而這就是裝飾器。
還是舉個(gè)例子,代碼如下:
#alist = [1, 2, 3, ..., 100] -- 1+2+3+...+100 = 5050
def lazy_sum():
return reduce(lambda x, y: x+y, alist)
我們定義了一個(gè)函數(shù)lazy_sum,作用是對(duì)alist中的所有元素求和后返回。alist假設(shè)為1到100的整數(shù)列表:
alist = range(1, 101)
但是出于某種原因,我并不想馬上返回計(jì)算結(jié)果,而是在之后的某個(gè)地方,通過(guò)顯示的調(diào)用輸出結(jié)果。于是我用一個(gè)wrapper函數(shù)對(duì)其進(jìn)行包裝:
def wrapper():
alist = range(1, 101)
def lazy_sum():
return reduce(lambda x, y: x+y, alist)
return lazy_sum
lazy_sum = wrapper() #wrapper() 返回的是lazy_sum函數(shù)對(duì)象
if __name__ == "__main__":
lazy_sum() #5050
這是一個(gè)典型的Lazy Evaluation的例子。我們知道,一般情況下,局部變量在函數(shù)返回時(shí),就會(huì)被垃圾回收器回收,而不能再被使用。但是這里的alist卻沒(méi)有,它隨著lazy_sum函數(shù)對(duì)象的返回被一并返回了(這個(gè)說(shuō)法不準(zhǔn)確,實(shí)際是包含在了lazy_sum的執(zhí)行環(huán)境中,通過(guò)__globals__),從而延長(zhǎng)了生命周期。
當(dāng)在if語(yǔ)句塊中調(diào)用lazy_sum()的時(shí)候,解析器會(huì)從上下文中(這里是Enclosing層的wrapper函數(shù)的局部作用域中)找到alist列表,計(jì)算結(jié)果,返回5050。
當(dāng)你需要?jiǎng)討B(tài)的給已定義的函數(shù)增加功能時(shí),比如:參數(shù)檢查,類似的原理就變得很有用:
def add(a, b):
return a+b
這是很簡(jiǎn)單的一個(gè)函數(shù):計(jì)算a+b的和返回,但我們知道Python是 動(dòng)態(tài)類型+強(qiáng)類型 的語(yǔ)言,你并不能保證用戶傳入的參數(shù)a和b一定是兩個(gè)整型,他有可能傳入了一個(gè)整型和一個(gè)字符串類型的值:
In [2]: add(1, 2)
Out[2]: 3
In [3]: add(1.2, 3.45)
Out[3]: 4.65
In [4]: add(5, 'hello')
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
/home/chiyu/ipython-input-4-f2f9e8aa5eae in module()
---- 1 add(5, 'hello')
/home/chiyu/ipython-input-1-02b3d3d6caec in add(a, b)
1 def add(a, b):
---- 2 return a+b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
于是,解析器無(wú)情的拋出了一個(gè)TypeError異常。
動(dòng)態(tài)類型:在運(yùn)行期間確定變量的類型,python確定一個(gè)變量的類型是在你第一次給他賦值的時(shí)候;
強(qiáng)類型:有強(qiáng)制的類型定義,你有一個(gè)整數(shù),除非顯示的類型轉(zhuǎn)換,否則絕不能將它當(dāng)作一個(gè)字符串(例如直接嘗試將一個(gè)整型和一個(gè)字符串做+運(yùn)算);
因此,為了更加優(yōu)雅的使用add函數(shù),我們需要在執(zhí)行+運(yùn)算前,對(duì)a和b進(jìn)行參數(shù)檢查。這時(shí)候裝飾器就顯得非常有用:
import logging
logging.basicConfig(level = logging.INFO)
def add(a, b):
return a + b
def checkParams(fn):
def wrapper(a, b):
if isinstance(a, (int, float)) and isinstance(b, (int, float)): #檢查參數(shù)a和b是否都為整型或浮點(diǎn)型
return fn(a, b) #是則調(diào)用fn(a, b)返回計(jì)算結(jié)果
#否則通過(guò)logging記錄錯(cuò)誤信息,并友好退出
logging.warning("variable 'a' and 'b' cannot be added")
return
return wrapper #fn引用add,被封存在閉包的執(zhí)行環(huán)境中返回
if __name__ == "__main__":
#將add函數(shù)對(duì)象傳入,fn指向add
#等號(hào)左側(cè)的add,指向checkParams的返回值wrapper
add = checkParams(add)
add(3, 'hello') #經(jīng)過(guò)類型檢查,不會(huì)計(jì)算結(jié)果,而是記錄日志并退出
注意checkParams函數(shù):
首先看參數(shù)fn,當(dāng)我們調(diào)用checkParams(add)的時(shí)候,它將成為函數(shù)對(duì)象add的一個(gè)本地(Local)引用;
在checkParams內(nèi)部,我們定義了一個(gè)wrapper函數(shù),添加了參數(shù)類型檢查的功能,然后調(diào)用了fn(a, b),根據(jù)LEGB法則,解釋器將搜索幾個(gè)作用域,并最終在(Enclosing層)checkParams函數(shù)的本地作用域中找到fn;
注意最后的return wrapper,這將創(chuàng)建一個(gè)閉包,fn變量(add函數(shù)對(duì)象的一個(gè)引用)將會(huì)封存在閉包的執(zhí)行環(huán)境中,不會(huì)隨著checkParams的返回而被回收;
當(dāng)調(diào)用add = checkParams(add)時(shí),add指向了新的wrapper對(duì)象,它添加了參數(shù)檢查和記錄日志的功能,同時(shí)又能夠通過(guò)封存的fn,繼續(xù)調(diào)用原始的add進(jìn)行+運(yùn)算。
因此調(diào)用add(3, 'hello')將不會(huì)返回計(jì)算結(jié)果,而是打印出日志:
chiyu@chiyu-PC:~$ python func.py
WARNING:root:variable 'a' and 'b' cannot be added
有人覺(jué)得add = checkParams(add)這樣的寫(xiě)法未免太過(guò)麻煩,于是python提供了一種更優(yōu)雅的寫(xiě)法,被稱為語(yǔ)法糖:
@checkParams
def add(a, b):
return a + b
這只是一種寫(xiě)法上的優(yōu)化,解釋器仍然會(huì)將它轉(zhuǎn)化為add = checkParams(add)來(lái)執(zhí)行。
6. 回歸問(wèn)題
def addspam(fn):
def new(*args):
print "spam,spam,spam"
return fn(*args)
return new
@addspam
def useful(a,b):
print a**2+b**2
首先看第二段代碼:
@addspam裝飾器,相當(dāng)于執(zhí)行了useful = addspam(useful)。在這里題主有一個(gè)理解誤區(qū):傳遞給addspam的參數(shù),是useful這個(gè)函數(shù)對(duì)象本身,而不是它的一個(gè)調(diào)用結(jié)果;
再回到addspam函數(shù)體:
return new 返回一個(gè)閉包,fn被封存在閉包的執(zhí)行環(huán)境中,不會(huì)隨著addspam函數(shù)的返回被回收;
而fn此時(shí)是useful的一個(gè)引用,當(dāng)執(zhí)行return fn(*args)時(shí),實(shí)際相當(dāng)于執(zhí)行了return useful(*args);
最后附上一張代碼執(zhí)行過(guò)程中的引用關(guān)系圖,希望能幫助你理解:
1、使用循環(huán)結(jié)構(gòu)
我們?cè)诰幊讨校绻切枰恢敝貜?fù)操作的話,比如說(shuō)重復(fù)的加法,重復(fù)發(fā)進(jìn)行運(yùn)算時(shí),我們就可以使用循環(huán)的結(jié)構(gòu),在這個(gè)過(guò)程中我們要確定的是就是循環(huán)的次數(shù),一般可以使用for循環(huán),舉個(gè)例子:
sum=0 for i in range(1,101): sum1 = sum1 +i i += 1 print("for--1-100的和是: ",sum1)
在這個(gè)例子中,就是對(duì)1-100之間的數(shù)進(jìn)行求和的操作,使用的就是for循環(huán)來(lái)進(jìn)行計(jì)算的,確定了循環(huán)的范圍是0-100。
2、使用圓形結(jié)構(gòu)
所謂的圓形結(jié)構(gòu),只不過(guò)是換了一種循環(huán)的方式,可以使用while循環(huán),使用這種方式是在不知道需要循環(huán)的次數(shù)時(shí)使用,在循環(huán)的時(shí)候,我們可以使用布爾值來(lái)控制循環(huán),如果獲得True,循環(huán)將繼續(xù)。如果獲得False,則循環(huán)終止。
網(wǎng)頁(yè)標(biāo)題:python如何恢復(fù)函數(shù) python數(shù)據(jù)恢復(fù)
標(biāo)題鏈接:http://jinyejixie.com/article46/dochhhg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供外貿(mào)建站、軟件開(kāi)發(fā)、企業(yè)網(wǎng)站制作、外貿(mào)網(wǎng)站建設(shè)、搜索引擎優(yōu)化、品牌網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)