https://litaotao.github.io/python-materials
1. 枚舉 - enumerate 可以有參數(shù)哦
之前我們這樣操作:
i=0
foriteminiterable:
printi,item
i+=1
現(xiàn)在我們這樣操作:
fori,iteminenumerate(iterable):
printi,item
enumerate函數(shù)還可以接收第二個參數(shù)。就像下面這樣:
>>>list(enumerate('abc'))
[(0,'a'),(1,'b'),(2,'c')]
>>>list(enumerate('abc',1))
[(1,'a'),(2,'b'),(3,'c')]
2. 字典/集合 解析
你也許知道如何進行列表解析,但是可能不知道字典/集合解析。它們簡單易用且高效。就像下面這個例子:
my_dict={i:i*iforiinxrange(100)}
my_set={i*15foriinxrange(100)}
# There is only a difference of ':' in both
# 兩者的區(qū)別在于字典推導中有冒號
3. 強制浮點除法
from__future__importdivision
result=1/2
# print(result)
# 0.5
4. 對Python表達式求值
我們都知道eval函數(shù),但是我們知道literal_eval函數(shù)么?也許很多人都不知道吧。可以用這種操作:
importast
my_list=ast.literal_eval(expr)
來代替以下這種操作:
expr="[1, 2, 3]"
my_list=eval(expr)
我相信對于大多數(shù)人來說這種形式是第一次看見,但是實際上這個在Python中已經(jīng)存在很長時間了。
5. 字符串/數(shù)列 逆序
你可以用以下方法快速逆序排列數(shù)列:
>>>a=[1,2,3,4]
>>>a[::-1]
[4,3,2,1]
# This creates a new reversed list.
# If you want to reverse a list in place you can do:
a.reverse()
這總方式也同樣適用于字符串的逆序:
>>>foo="yasoob"
>>>foo[::-1]
'boosay'
6. 三元運算
三元運算是if-else 語句的快捷操作,也被稱為條件運算。這里有幾個例子可以供你參考,它們可以讓你的代碼更加緊湊,更加美觀。
[on_true]if[expression]else[on_false]
x,y=50,25
small=xifx
7. Python里面如何拷貝一個對象
標準庫中的copy模塊提供了兩個方法來實現(xiàn)拷貝.一個方法是copy,它返回和參數(shù)包含內(nèi)容一樣的對象.
importcopy
new_list=copy.copy(existing_list)
有些時候,你希望對象中的屬性也被復制,可以使用deepcopy方法:
importcopy
new_list_of_dicts=copy.deepcopy(existing_list_of_dicts)
copy(x)
ShallowcopyoperationonarbitraryPythonobjects.
deepcopy(x,memo=None,_nil=[])
DeepcopyoperationonarbitraryPythonobjects.
8. python中如何判斷對象相等
首先是C#中字符串的==和equal方法。
“==”:
對于內(nèi)置值類型而言,==判斷兩個內(nèi)存值是否相等。
對于用戶自定義的值類型而言(Struct),==需要重載,否則不能使用。
對于引用類型而言,默認是同一引用才返回true,但是系統(tǒng)重載了很多引用類型的==(比如下文提到的string),所以c#中引用類型的比較并不建議使用 ==。
“equals”:
對于值類型而言,內(nèi)存相等才返回true。
對于引用類型而言,指向同一個引用才算相等。
但是比較特殊的是字符串String,是一個特殊的引用型類型,在C#語言中,重載了string的equals()方法,使string對象用起來就像是值類型一樣。
python中的==
python中的對象包含三要素:id,type,value
id用來標識唯一一個對象,type標識對象的類型,value用來設置對象的值。
is判斷是否是一個對象,使用id來判斷的。
==是判斷a對象的值是否是b對象的值,默認調(diào)用它的__eq__方法。
9. 命名技巧
今天閱讀代碼,發(fā)現(xiàn)一個不錯的函數(shù)命名方式:
defrequest(_argv):
就是把所有的參數(shù)前面都加上_下劃線,這樣你在函數(shù)體中,一眼就可以看出那些是局部變量,那些是作為參數(shù)傳入的,類似把全局變量前面加上g。
10. 開發(fā)者工具集錦
pydoc: 模塊可以根據(jù)源代碼中的docstrings為任何可導入模塊生成格式良好的文檔。
doctest模塊:該模塊可以從源代碼或獨立文件的例子中抽取出測試用例。
unittest模塊:該模塊是一個全功能的自動化測試框架,該框架提供了對測試準備(test fixtures), 預定義測試集(predefined test suite)以及測試發(fā)現(xiàn)(test discovery)的支持。
trace:模塊可以監(jiān)控Python執(zhí)行程序的方式,同時生成一個報表來顯示程序的每一行執(zhí)行的次數(shù)。這些信息可以用來發(fā)現(xiàn)未被自動化測試集所覆蓋的程序執(zhí)行路徑,也可以用來研究程序調(diào)用圖,進而發(fā)現(xiàn)模塊之間的依賴關系。編寫并執(zhí)行測試可以發(fā)現(xiàn)絕大多數(shù)程序中的問題,Python使得debug工作變得更加簡單,這是因為在大部分情況下,Python都能夠將未被處理的錯誤打印到控制臺中,我們稱這些錯誤信息為traceback。如果程序不是在文本控制臺中運行的,traceback也能夠將錯誤信息輸出到日志文件或是消息對話框中。當標準的traceback無法提供足夠的信息時,可以使用cgitb 模塊來查看各級棧和源代碼上下文中的詳細信息,比如局部變量。cgitb模塊還能夠將這些跟蹤信息以HTML的形式輸出,用來報告web應用中的錯誤。
pdb:該模塊可以顯示出程序在錯誤產(chǎn)生時的執(zhí)行路徑,同時可以動態(tài)地調(diào)整對象和代碼進行調(diào)試。
profile, timeit: 開發(fā)者可以使用profile以及timit模塊來測試程序的速度,找出程序中到底是哪里很慢,進而對這部分代碼獨立出來進行調(diào)優(yōu)的工作。
compileall: Python程序是通過解釋器執(zhí)行的,解釋器的輸入是原有程序的字節(jié)碼編譯版本。這個字節(jié)碼編譯版本可以在程序執(zhí)行時動態(tài)地生成,也可以在程序打包的時候就生成。compileall模塊可以處理程序打包的事宜,它暴露出了打包相關的接口,該接口能夠被安裝程序和打包工具用來生成包含模塊字節(jié)碼的文件。同時,在開發(fā)環(huán)境中,compileall模塊也可以用來驗證源文件是否包含了語法錯誤。
YAPF:Google開源的Python代碼格式化工具。
iPDB: iPDB是一個極好的工具,我已經(jīng)用它查出了很多匪夷所思的bug。pip install ipdb 安裝該工具,然后在你的代碼中import ipdb; ipdb.set_trace(),然后你會在你的程序運行時,獲得一個很好的交互式提示。它每次執(zhí)行程序的一行并且檢查變量。
pycallgraph: 在一些場合,我使用pycallgraph來追蹤性能問題。它可以創(chuàng)建函數(shù)調(diào)用時間和次數(shù)的圖表。
objgraph: objgraph對于查找內(nèi)存泄露非常有用。
11. Python代碼微優(yōu)化之加快查找
collections.OrderedDict類:
def__setitem__(self,key,value,dict_setitem=dict.__setitem__):
ifkeynotinself:
root=self.__root
last=root[0]
last[1]=root[0]=self.__map[key]=[last,root,key]
returndict_setitem(self,key,value)
注意最后一個參數(shù):dict_setitem=dict.setitem。如果你仔細想就會感覺有道理。將值關聯(lián)到鍵上,你只需要給__setitem__傳遞三個參數(shù):要設置的鍵,與鍵關聯(lián)的值,傳遞給內(nèi)建dict類的__setitem__類方法。等會,好吧,也許最后一個參數(shù)沒什么意義。 最后一個參數(shù)其實是將一個函數(shù)綁定到局部作用域中的一個函數(shù)上。具體是通過將dict.__setitem__賦值為參數(shù)的默認值。這里還有另一個例子:
defnot_list_or_dict(value):
returnnot(isinstance(value,dict)orisinstance(value,list))
defnot_list_or_dict(value,_isinstance=isinstance,_dict=dict,_list=list):
returnnot(_isinstance(value,_dict)or_isinstance(value,_list))
這里我們做同樣的事情,把本來將會在內(nèi)建命名空間中的對象綁定到局部作用域中去。因此,python將會使用LOCAL_FAST而不是LOAD_GLOBAL(全局查找)。那么這到底有多快呢?我們做個簡單的測試:
$python-mtimeit-s'def not_list_or_dict(value): return not (isinstance(value, dict) or isinstance(value, list))''not_list_or_dict(50)'
1000000loops,bestof3:0.48usecperloop
$python-mtimeit-s'def not_list_or_dict(value, _isinstance=isinstance, _dict=dict, _list=list): return not (_isinstance(value, _dict) or _isinstance(value, _list))''not_list_or_dict(50)'
1000000loops,bestof3:0.423usecperloop
換句話說,大概有11.9%的提升 [2]。比我在文章開始處承諾的5%還多!
12. 包管理
Python世界最棒的地方之一,就是大量的第三方程序包。同樣,管理這些包也非常容易。按照慣例,會在 requirements.txt 文件中列出項目所需要的包。每個包占一行,通常還包含版本號。
pelican==3.3
Markdown
pelican-extended-sitemap==1.0.0
13. Python函數(shù)參數(shù)默認值的陷阱和原理深究
Python2.7.9(default,Dec192014,06:05:48)
[GCC4.2.1CompatibleAppleLLVM6.0(clang-600.0.56)]ondarwin
Type"help","copyright","credits"or"license"formoreinformation.
>>>defgenerate_new_list_with(my_list=[],element=None):
...my_list.append(element)
...returnmy_list
...
>>>list_1=generate_new_list_with(element=1)
>>>list_1
[1]
>>>list_2=generate_new_list_with(element=2)
>>>list_2
[1,2]
>>>
可見代碼運行結果并不和我們預期的一樣。list_2在函數(shù)的第二次調(diào)用時并沒有得到一個新的list并填入2,而是在第一次調(diào)用結果的基礎上append了一個2。為什么會發(fā)生這樣在其他編程語言中簡直就是設計bug一樣的問題呢?
可見如果參數(shù)默認值是在函數(shù)編譯compile階段就已經(jīng)被確定。之后所有的函數(shù)調(diào)用時,如果參數(shù)不顯示的給予賦值,那么所謂的參數(shù)默認值不過是一個指向那個在compile階段就已經(jīng)存在的對象的指針。如果調(diào)用函數(shù)時,沒有顯示指定傳入?yún)?shù)值得話。那么所有這種情況下的該參數(shù)都會作為編譯時創(chuàng)建的那個對象的一種別名存在。如果參數(shù)的默認值是一個不可變(Imuttable)數(shù)值,那么在函數(shù)體內(nèi)如果修改了該參數(shù),那么參數(shù)就會重新指向另一個新的不可變值。而如果參數(shù)默認值是和本文最開始的舉例一樣,是一個可變對象(Muttable),那么情況就比較糟糕了。所有函數(shù)體內(nèi)對于該參數(shù)的修改,實際上都是對compile階段就已經(jīng)確定的那個對象的修改。
14. 單下劃線(_)
1、在解釋器中:在這種情況下,“_”代表交互式解釋器會話中上一條執(zhí)行的語句的結果。這種用法首先被標準CPython解釋器采用,然后其他類型的解釋器也先后采用。
>>>_Traceback(mostrecentcalllast):
File"",line1,in
NameError:name'_'isnotdefined
>>>42
>>>_
42
>>>'alright!'if_else':('
'alright!'
>>>_
'alright!'
2、作為一個名稱:這與上面一點稍微有些聯(lián)系,此時“”作為臨時性的名稱使用。這樣,當其他人閱讀你的代碼時將會知道,你分配了一個特定的名稱,但是并不會在后面再次用到該名稱。例如,下面的例子中,你可能對循環(huán)計數(shù)中的實際值并不感興趣,此時就可以使用“”。
n=42
for_inrange(n):
do_something()
3、國際化:也許你也曾看到”_“會被作為一個函數(shù)來使用。這種情況下,它通常用于實現(xiàn)國際化和本地化字符串之間翻譯查找的函數(shù)名稱,這似乎源自并遵循相應的C約定。例如,在Django文檔“轉換”章節(jié)中,你將能看到如下代碼:
fromdjango.utils.translationimportugettextas_
fromdjango.httpimportHttpResponse
defmy_view(request):
output=_("Welcome to my site.")
returnHttpResponse(output)
可以發(fā)現(xiàn),場景二和場景三中的使用方法可能會相互沖突,所以我們需要避免在使用“”作為國際化查找轉換功能的代碼塊中同時使用“”作為臨時名稱。
15. 名稱前的單下劃線(如:_shahriar)
程序員使用名稱前的單下劃線,用于指定該名稱屬性為“私有”。這有點類似于慣例,為了使其他人(或你自己)使用這些代碼時將會知道以“_”開頭的名稱只供內(nèi)部使用。正如Python文檔中所述:
以下劃線 __ 為前綴的名稱(如_pam)應該被視為API中非公開的部分(不管是函數(shù)、方法還是數(shù)據(jù)成員)。此時,應該將它們看作是一種實現(xiàn)細節(jié),在修改它們時無需對外部通知。
正如上面所說,這確實類似一種慣例,因為它對解釋器來說確實有一定的意義,如果你寫了代碼 :from <模塊/包名> import *,那么以 _ 開頭的名稱都不會被導入,除非模塊或包中的__all__列表顯式地包含了它們。了解更多請查看Importing * in Python
16. 名稱前的雙下劃線(如:__shahriar)
名稱(具體為一個方法名)前雙下劃線 _ 的用法并不是一種慣例,對解釋器來說它有特定的意義。Python中的這種用法是為了避免與子類定義的名稱沖突。Python文檔指出,__spam 這種形式(至少兩個前導下劃線,最多一個后續(xù)下劃線)的任何標識符將會被 正如所預料的,“_internal_use”并未改變,而“__method_name”卻被變成了“_ClassName__method_name”。此時,如果你創(chuàng)建A的一個子類B,那么你將不能輕易地覆寫A中的方法“__method_name”。spam 這種形式原文取代,在這里 classname 是去掉前導下劃線的當前類名。例如下面的例子:
>>>classA(object):
...def_internal_use(self):
...pass
...def__method_name(self):
...pass
...
>>>dir(A())
['_A__method_name',...,'_internal_use']
正如所預料的,“_internal_use”并未改變,而“__method_name”卻被變成了“_ClassName__method_name”。此時,如果你創(chuàng)建A的一個子類B,那么你將不能輕易地覆寫A中的方法“__method_name”。
17. 名稱前后的雙下劃線(如:init)
這種用法表示Python中特殊的方法名。其實,這只是一種慣例,對Python系統(tǒng)來說,這將確保不會與用戶自定義的名稱沖突。通常,你將會覆寫這些方法,并在里面實現(xiàn)你所需要的功能,以便Python調(diào)用它們。例如,當定義一個類時,你經(jīng)常會覆寫“init”方法。
雖然你也可以編寫自己的特殊方法名,但不要這樣做。
17. 隱藏特性 1,函數(shù)unpack
deffoo(x,y):
printx,y
alist=[1,2]
adict={'x':1,'y':2}
foo(*alist)# 1, 2
foo(**adict)# 1, 2
18. 隱藏特性 2, 鏈式比較操作符
>>>x=3
>>>1
True
>>>4>x>=3
True
19. 隱藏特性 3,函數(shù)的默認參數(shù)
>>>deffoo(x=[]):
...x.append(1)
...printx
...
>>>foo()
[1]
>>>foo()
[1,1]
更安全的做法是:
>>>deffoo(x=None):
...ifxisNone:
...x=[]
...x.append(1)
...printx
...
>>>foo()
[1]
>>>foo()
[1]
>>>
20. 隱藏特性 4,字典的get方法
21. 隱藏特性 5,帶關鍵字的格式化
>>>print"Hello%(name)s !"%{'name':'James'}
HelloJames!
>>>print"I am years%(age)i years old"%{'age':18}
Iamyears18yearsold
更新些的格式化:
>>>print"Hello {name} !".format(name="James")
HelloJames!
22. 隱藏特性 6,切片操作的步長參數(shù)
可以用步長 -1 來反轉鏈表:
>>>a=[1,2,3,4,5]
>>>a[::2]
[1,3,5]
>>>a[::-1]
[5,4,3,2,1]
>>>
23. 隱藏特性 7,嵌套列表推導式
[(i,j)foriinrange(3)forjinrange(i)]
[(1,0),(2,0),(2,1)]
列表推導構造permutation:
可以用 itertools.permutations 來實現(xiàn)。
In[47]:a='abcd'
In[48]:[i+j+kforiinaforjina.replace(i,'')forkina.replace(i,'').replace(j,'')]
Out[48]:
['abc',
'abd',
'acb',
'acd',
'adb',
'adc',
'bac',
'bad',
'bca',
'bcd',
'bda',
'bdc',
'cab',
'cad',
'cba',
'cbd',
'cda',
'cdb',
'dab',
'dac',
'dba',
'dbc',
'dca',
'dcb']
24. 隱藏特性 8,print 重定向輸出到文件
注意打開的模式: “w+” 而不能 “w” , 當然 “a” 是可以的
>>>print>>open("somefile","w+"),"Hello World"
25. 隱藏特性 9, Python3中的元組unpack
>>>a,b,*rest=range(10)
>>>a
0
>>>b
1
>>>rest
[2,3,4,5,6,7,8,9]
>>>
>>>first,second,*rest,last=range(10)
>>>first
0
>>>second
1
>>>last
9
>>>rest
[2,3,4,5,6,7,8]
26. 隱藏特性 10,pow的第三個參數(shù)
其實第三個參數(shù)是來求模的: pow(x, y, z) == (x ** y) % z,注意,內(nèi)置的 pow 和 math.pow 并不是一個函數(shù),后者只接受2個參數(shù)。
>>>pow(4,2,2)
0
>>>pow(4,2,3)
1
27. 隱藏特性 11,enumerate還有第二個參數(shù)?
enumerate 很贊,可以給我們索引和序列值的對, 但是它還有第二個參數(shù),這個參數(shù)用來: 指明索引的起始值。
>>>lst=["a","b","c"]
>>>list(enumerate(lst,1))
[(1,'a'),(2,'b'),(3,'c')]
28. 隱藏特性 12,顯式的聲明一個集合
在Python 2.7 之后可以這么聲明一個集合。
>>>{1,2,3}
set([1,2,3])
29. 隱藏特性 13,用切片來刪除序列的某一段
>>>a=[1,2,3,4,5,6,7]
>>>a[1:4]=[]
>>>a
[1,5,6,7]
當然用 del a[1:4] 也是可以的,去除偶數(shù)項(偶數(shù)索引的):
>>>a=[0,1,2,3,4,5,6,7]
>>>dela[::2]
>>>a
[1,3,5,7]
30. 隱藏特性 14,isinstance可以接收一個元組
這個真的鮮為人知, 我們可以用 isinstance(x, (float, int)) 來判斷 x 是不是數(shù),也就是那個元組里面是 或 的關系,只要是其中一個的實例就返回 True。
>>>isinstance(1,(float,int))
True
>>>isinstance(1.3,(float,int))
True
>>>isinstance("1.3",(float,int))
False
31. 讓關鍵代碼依賴于外部包
雖然Python讓許多編程任務變得容易,但它可能并不總能為緊急的任務提供最佳性能。你可以為緊急的任務使用C、C++或機器語言編寫的外部包,這樣可以提高應用程序的性能。這些包都是不能跨平臺的,這意味著你需要根據(jù)你正在使用的平臺,尋找合適的包。簡而言之,這個方案放棄了一些應用程序的可移植性,以換取只有在特定主機上直接編程才能獲得的程序性能。這里有一些你應該考慮加入到你的“性能兵工廠”的包:
Cython
PyInlne
PyPy
Pyrex
這些包以不同的方式提高性能。例如,Pyrex能夠擴展Python所能做的事情,例如使用C的數(shù)據(jù)類型來讓內(nèi)存任務更加有效或直接。PyInIne讓你在Python應用程序中直接使用C代碼。程序中的內(nèi)聯(lián)代碼單獨編譯,但它在利用C語言所能提供的效率的同時,也讓所有的代碼都在同一個地方。
32. 排序時使用鍵(key)
有很多老的Python排序代碼,它們在你創(chuàng)建一個自定義的排序時花費你的時間,但在運行時確實能加速執(zhí)行排序過程。元素排序的最好方法是盡可能使用鍵(key)和默認的sort()排序方法。例如,考慮下面的代碼:
importoperator
somelist=[(1,5,8),(6,2,4),(9,7,5)]
somelist.sort(key=operator.itemgetter(0))
somelist
#Output = [(1, 5, 8), (6, 2, 4), (9, 7, 5)]
somelist.sort(key=operator.itemgetter(1))
somelist
#Output = [(6, 2, 4), (1, 5, 8), (9, 7, 5)]
somelist.sort(key=operator.itemgetter(2))
somelist
每一個實例中,根據(jù)你選擇的作為key參數(shù)部分的索引,數(shù)組進行了排序。類似于利用數(shù)字進行排序,這種方法同樣適用于利用字符串排序。
33. 優(yōu)化循環(huán)
每種編程語言都會強調(diào)需要優(yōu)化循環(huán)。當使用Python的時候,你可以依靠大量的技巧使得循環(huán)運行得更快。然而,開發(fā)者經(jīng)常漏掉的一個方法是:避免在一個循環(huán)中使用點操作。例如,考慮下面的代碼:
lowerlist=['this','is','lowercase']
upper=str.upper
upperlist=[]
append=upperlist.append
forwordinlowerlist:
append(upper(word))
print(upperlist)
#Output = ['THIS', 'IS', 'LOWERCASE']
每一次你調(diào)用方法str.upper,Python都會求該方法的值。然而,如果你用一個變量代替求得的值,值就變成了已知的,Python就可以更快地執(zhí)行任務。優(yōu)化循環(huán)的關鍵,是要減少Python在循環(huán)內(nèi)部執(zhí)行的工作量,因為Python原生的解釋器在那種情況下,真的會減緩執(zhí)行的速度。
(注意:優(yōu)化循環(huán)的方法有很多,這只是其中的一個。例如,許多程序員都會說,列表推導是在循環(huán)中提高執(zhí)行速度的最好方式。這里的關鍵是,優(yōu)化循環(huán)是程序取得更高的執(zhí)行速度的更好方式之一。)
34. 嘗試多種編碼方法
如果每次你創(chuàng)建一個應用程序都是用相同的編碼方法,幾乎肯定會導致一些你的應用程序比它能夠達到的運行效率慢的情況。作為分析過程的一部分,你可以嘗試一些實驗。例如,在一個字典中管理一些元素,你可以采用安全的方法確定元素是否已經(jīng)存在并更新,或者你可以直接添加元素,然后作為異常處理該元素不存在情況。考慮第一個編碼的例子:
n=16
myDict={}
foriinrange(0,n):
char='abcd'[i%4]
ifcharnotinmyDict:
myDict[char]=0
myDict[char]+=1
print(myDict)
這段代碼通常會在myDict開始為空時運行得更快。然而,當mydict通常被數(shù)據(jù)填充(或者至少大部分被充填)時,另一種方法效果更好。
n=16
myDict={}
foriinrange(0,n):
char='abcd'[i%4]
try:
myDict[char]+=1
exceptKeyError:
myDict[char]=1
print(myDict)
兩種情況下具有相同的輸出:{‘d’: 4, ‘c’: 4, ‘b’: 4, ‘a(chǎn)’: 4}。唯一的不同是這個輸出是如何得到的。跳出固定的思維模式,創(chuàng)造新的編碼技巧,能夠幫助你利用你的應用程序獲得更快的結果。
35. 使用列表推導式
一個列表推導式包含以下幾個部分:
一個輸入序列
一個表示輸入序列成員的變量
一個可選的斷言表達式
一個將輸入序列中滿足斷言表達式的成員變換成輸出列表成員的輸出表達式
num=[1,4,-5,10,-7,2,3,-1]
filtered_and_squared=[]
fornumberinnum:
ifnumber>0:
filtered_and_squared.append(number**2)
printfiltered_and_squared
# [1, 16, 100, 4, 9]
而如果使用filter、lambda和map函數(shù),則能夠將代碼大大簡化:
num=[1,4,-5,10,-7,2,3,-1]
filtered_and_squared=map(lambdax:x**2,filter(lambdax:x>0,num))
printfiltered_and_squared
# [1, 16, 100, 4, 9]
## 更簡化的一種寫法
num=[1,4,-5,10,-7,2,3,-1]
filtered_and_squared=[x**2forxinnumifx>0]
printfiltered_and_squared
# [1, 16, 100, 4, 9]
列表推導也可能會有一些負面效應,那就是整個列表必須一次性加載于內(nèi)存之中,這對上面舉的例子而言不是問題,甚至擴大若干倍之后也都不是問題。但是總會達到極限,內(nèi)存總會被用完。
針對上面的問題,生成器(Generator)能夠很好的解決。生成器表達式不會一次將整個列表加載到內(nèi)存之中,而是生成一個生成器對象(Generator objector),所以一次只加載一個列表元素。
生成器表達式同列表推導式有著幾乎相同的語法結構,區(qū)別在于生成器表達式是被圓括號包圍,而不是方括號:
num=[1,4,-5,10,-7,2,3,-1]
filtered_and_squared=(x**2forxinnumifx>0)
printfiltered_and_squared
# at 0x00583E18>
foriteminfiltered_and_squared:
printitem
# 1, 16, 100 4,9
這比列表推導效率稍微提高一些,讓我們再一次改造一下代碼:
num=[1,4,-5,10,-7,2,3,-1]
defsquare_generator(optional_parameter):
return(x**2forxinnumifx>optional_parameter)
printsquare_generator(0)
# at 0x004E6418>
# Option I
forkinsquare_generator(0):
printk
# 1, 16, 100, 4, 9
# Option II
g=list(square_generator(0))
printg
# [1, 16, 100, 4, 9]
除非特殊的原因,應該經(jīng)常在代碼中使用生成器表達式。但除非是面對非常大的列表,否則是不會看出明顯區(qū)別的。 再來看一個通過兩階列表推導式遍歷目錄的例子:
importos
deftree(top):
forpath,names,fnamesinos.walk(top):
forfnameinfnames:
yieldos.path.join(path,fname)
fornameintree('C:\Users\XXX\Downloads\Test'):
printname
36. 裝飾器(Decorators)
裝飾器為我們提供了一個增加已有函數(shù)或類的功能的有效方法。聽起來是不是很像Java中的面向切面編程(Aspect-Oriented Programming)概念?兩者都很簡單,并且裝飾器有著更為強大的功能。舉個例子,假定你希望在一個函數(shù)的入口和退出點做一些特別的操作(比如一些安全、追蹤以及鎖定等操作)就可以使用裝飾器。
裝飾器是一個包裝了另一個函數(shù)的特殊函數(shù):主函數(shù)被調(diào)用,并且其返回值將會被傳給裝飾器,接下來裝飾器將返回一個包裝了主函數(shù)的替代函數(shù),程序的其他部分看到的將是這個包裝函數(shù)。
importtime
fromfunctoolsimportwraps
deftimethis(func):
'''
Decorator that reports the execution time.
'''
@wraps(func)
defwrapper(*args,**kwargs):
start=time.time()
result=func(*args,**kwargs)
end=time.time()
print(func.__name__,end-start)
returnresult
returnwrapper
@timethis
defcountdown(n):
whilen>0:
n-=1
countdown(100000)
# ('countdown', 0.006999969482421875)
37. 上下文管理庫(ContextLib)
contextlib模塊包含了與上下文管理器和with聲明相關的工具。通常如果你想寫一個上下文管理器,則你需要定義一個類包含__enter__方法以及__exit__方法,例如:
importtime
classdemo:
def__init__(self,label):
self.label=label
def__enter__(self):
self.start=time.time()
def__exit__(self,exc_ty,exc_val,exc_tb):
end=time.time()
print('{}: {}'.format(self.label,end-self.start))
完整的例子在此:
importtime
classdemo:
def__init__(self,label):
self.label=label
def__enter__(self):
self.start=time.time()
def__exit__(self,exc_ty,exc_val,exc_tb):
end=time.time()
print('{}: {}'.format(self.label,end-self.start))
withdemo('counting'):
n=10000000
whilen>0:
n-=1
# counting: 1.36000013351
上下文管理器被with聲明所激活,這個API涉及到兩個方法。
__enter__方法,當執(zhí)行流進入with代碼塊時,__enter__方法將執(zhí)行。并且它將返回一個可供上下文使用的對象。
當執(zhí)行流離開with代碼塊時,__exit__方法被調(diào)用,它將清理被使用的資源。
利用@contextmanager裝飾器改寫上面那個例子:
fromcontextlibimportcontextmanager
importtime
@contextmanager
defdemo(label):
start=time.time()
try:
yield
finally:
end=time.time()
print('{}: {}'.format(label,end-start))
withdemo('counting'):
n=10000000
whilen>0:
n-=1
# counting: 1.32399988174
看上面這個例子,函數(shù)中yield之前的所有代碼都類似于上下文管理器中__enter__方法的內(nèi)容。而yield之后的所有代碼都如__exit__方法的內(nèi)容。如果執(zhí)行過程中發(fā)生了異常,則會在yield語句觸發(fā)。
38. 描述器(Descriptors)
描述器決定了對象屬性是如何被訪問的。描述器的作用是定制當你想引用一個屬性時所發(fā)生的操作。
構建描述器的方法是至少定義以下三個方法中的一個。需要注意,下文中的instance是包含被訪問屬性的對象實例,而owner則是被描述器修辭的類。
get(self, instance, owner) – 這個方法是當屬性被通過(value = obj.attr)的方式獲取時調(diào)用,這個方法的返回值將被賦給請求此屬性值的代碼部分。set(self, instance, value) – 這個方法是當希望設置屬性的值(obj.attr = ‘value’)時被調(diào)用,該方法不會返回任何值。delete(self, instance) – 當從一個對象中刪除一個屬性時(del obj.attr),調(diào)用此方法。 譯者注:對于instance和owner的理解,考慮以下代碼:
classCelsius(object):
def__init__(self,value=0.0):
self.value=float(value)
def__get__(self,instance,owner):
returnself.value
def__set__(self,instance,value):
self.value=float(value)
classTemperature(object):
celsius=Celsius()
temp=Temperature()
temp.celsius#calls Celsius.__get__
39. Zipping and unzipping lists and iterables
>>>a=[1,2,3]
>>>b=['a','b','c']
>>>z=zip(a,b)
>>>z
[(1,'a'),(2,'b'),(3,'c')]
>>>zip(*z)
[(1,2,3),('a','b','c')]
40. Grouping adjacent list items using zip
>>>a=[1,2,3,4,5,6]
>>># Using iterators
>>>group_adjacent=lambdaa,k:zip(*([iter(a)]*k))
>>>group_adjacent(a,3)
[(1,2,3),(4,5,6)]
>>>group_adjacent(a,2)
[(1,2),(3,4),(5,6)]
>>>group_adjacent(a,1)
[(1,),(2,),(3,),(4,),(5,),(6,)]
>>># Using slices
>>>fromitertoolsimportislice
>>>group_adjacent=lambdaa,k:zip(*(islice(a,i,None,k)foriinrange(k)))
>>>group_adjacent(a,3)
[(1,2,3),(4,5,6)]
>>>group_adjacent(a,2)
[(1,2),(3,4),(5,6)]
>>>group_adjacent(a,1)
[(1,),(2,),(3,),(4,),(5,),(6,)]
41. Sliding windows (n-grams) using zip and iterators
>>>fromitertoolsimportislice
>>>defn_grams(a,n):
...z=(islice(a,i,None)foriinrange(n))
...returnzip(*z)
...
>>>a=[1,2,3,4,5,6]
>>>n_grams(a,3)
[(1,2,3),(2,3,4),(3,4,5),(4,5,6)]
>>>n_grams(a,2)
[(1,2),(2,3),(3,4),(4,5),(5,6)]
>>>n_grams(a,4)
[(1,2,3,4),(2,3,4,5),(3,4,5,6)]
42. Inverting a dictionary using zip
>>>m={'a':1,'b':2,'c':3,'d':4}
>>>m.items()
[('a',1),('c',3),('b',2),('d',4)]
>>>zip(m.values(),m.keys())
[(1,'a'),(3,'c'),(2,'b'),(4,'d')]
>>>mi=dict(zip(m.values(),m.keys()))
>>>mi
{1:'a',2:'b',3:'c',4:'d'}
43. Flattening lists
>>>a=[[1,2],[3,4],[5,6]]
>>>list(itertools.chain.from_iterable(a))
[1,2,3,4,5,6]
>>>sum(a,[])
[1,2,3,4,5,6]
>>>[xforlinaforxinl]
[1,2,3,4,5,6]
>>>a=[[[1,2],[3,4]],[[5,6],[7,8]]]
>>>[xforl1inaforl2inl1forxinl2]
[1,2,3,4,5,6,7,8]
>>>a=[1,2,[3,4],[[5,6],[7,8]]]
>>>flatten=lambdax:[yforlinxforyinflatten(l)]iftype(x)islistelse[x]
>>>flatten(a)
[1,2,3,4,5,6,7,8]
44. Dictionary comprehensions
>>>m={x:x**2forxinrange(5)}
>>>m
{0:0,1:1,2:4,3:9,4:16}
>>>m={x:'A'+str(x)forxinrange(10)}
>>>m
{0:'A0',1:'A1',2:'A2',3:'A3',4:'A4',5:'A5',6:'A6',7:'A7',8:'A8',9:'A9'}
45. 常犯錯誤,濫用表達式作為函數(shù)參數(shù)默認值
Python允許開發(fā)者指定一個默認值給函數(shù)參數(shù),雖然這是該語言的一個特征,但當參數(shù)可變時,很容易導致混亂,例如,下面這段函數(shù)定義:
>>>deffoo(bar=[]):# bar is optional and defaults to [] if not specified
...bar.append("baz")# but this line could be problematic, as we'll see...
...returnbar
在上面這段代碼里,一旦重復調(diào)用foo()函數(shù)(沒有指定一個bar參數(shù)),那么將一直返回’bar’,因為沒有指定參數(shù),那么foo()每次被調(diào)用的時候,都會賦予[]。下面來看看,這樣做的結果:
>>>foo()
["baz"]
>>>foo()
["baz","baz"]
>>>foo()
["baz","baz","baz"]
解決方案:
>>>deffoo(bar=None):
...ifbarisNone:# or if not bar:
...bar=[]
...bar.append("baz")
...returnbar
...
>>>foo()
["baz"]
>>>foo()
["baz"]
>>>foo()
["baz"]
46. 誤解Python規(guī)則范圍
Python的作用域解析是基于LEGB規(guī)則,分別是Local、Enclosing、Global、Built-in。實際上,這種解析方法也有一些玄機,看下面這個例子:
>>>x=10
>>>deffoo():
...x+=1
...printx
...
>>>foo()
Traceback(mostrecentcalllast):
File"",line1,in
File"",line2,infoo
UnboundLocalError:localvariable'x'referencedbeforeassignment
許多人會感動驚訝,當他們在工作的函數(shù)體里添加一個參數(shù)語句,會在先前工作的代碼里報UnboundLocalError錯誤( 點擊這里查看更詳細描述)。 在使用列表時,開發(fā)者是很容易犯這種錯誤的,看看下面這個例子:
>>>lst=[1,2,3]
>>>deffoo1():
...lst.append(5)# This works ok...
...
>>>foo1()
>>>lst
[1,2,3,5]
>>>lst=[1,2,3]
>>>deffoo2():
...lst+=[5]# ... but this bombs!
...
>>>foo2()
Traceback(mostrecentcalllast):
File"",line1,in
File"",line2,infoo
UnboundLocalError:localvariable'lst'referencedbeforeassignment
為什么foo2失敗而foo1運行正常? 答案與前面那個例子是一樣的,但又有一些微妙之處。foo1沒有賦值給lst,而foo2賦值了。lst += [5]實際上就是lst = lst + [5],試圖給lst賦值(因此,假設Python是在局部作用域里)。然而,我們正在尋找指定給lst的值是基于lst本身,其實尚未確定。
47. 修改遍歷列表
>>>odd=lambdax:bool(x%2)
>>>numbers=[nforninrange(10)]
>>>foriinrange(len(numbers)):
...ifodd(numbers[i]):
...delnumbers[i]# BAD: Deleting item from a list while iterating over it
...
Traceback(mostrecentcalllast):
File"",line2,in
IndexError:listindexoutofrange
在遍歷的時候,對列表進行刪除操作,這是很低級的錯誤。稍微有點經(jīng)驗的人都不會犯。 對上面的代碼進行修改,正確地執(zhí)行:
>>>odd=lambdax:bool(x%2)
>>>numbers=[nforninrange(10)]
>>>numbers[:]=[nforninnumbersifnotodd(n)]# ahh, the beauty of it all
>>>numbers
[0,2,4,6,8]
48. 合理使用copy與deepcopy
對于dict和list等數(shù)據(jù)結構的對象,直接賦值使用的是引用的方式。而有些情況下需要復制整個對象,這時可以使用copy包里的copy和deepcopy,這兩個函數(shù)的不同之處在于后者是遞歸復制的。效率也不一樣:(以下程序在ipython中運行)
timeit后面的-n表示運行的次數(shù),后兩行對應的是兩個timeit的輸出,下同。由此可見后者慢一個數(shù)量級。
importcopy
a=range(100000)
%timeit-n10copy.copy(a)# 運行10次 copy.copy(a)
%timeit-n10copy.deepcopy(a)
10loops,bestof3:1.55msperloop
10loops,bestof3:151msperloop
49. 合理使用生成器(generator)和yield
%timeit-n100a=(iforiinrange(100000))
%timeit-n100b=[iforiinrange(100000)]
100loops,bestof3:1.54msperloop
100loops,bestof3:4.56msperloop
使用()得到的是一個generator對象,所需要的內(nèi)存空間與列表的大小無關,所以效率會高一些。在具體應用上,比如set(i for i in range(100000))會比set([i for i in range(100000)])快。
但是對于需要循環(huán)遍歷的情況:
%timeit-n10forxin(iforiinrange(100000)):pass
%timeit-n10forxin[iforiinrange(100000)]:pass
10loops,bestof3:6.51msperloop
10loops,bestof3:5.54msperloop
后者的效率反而更高,但是如果循環(huán)里有break,用generator的好處是顯而易見的。yield也是用于創(chuàng)建generator:
50. 使用級聯(lián)比較x < y < z
x,y,z=1,2,3
%timeit-n1000000ifx
%timeit-n1000000ifx
1000000loops,bestof3:101nsperloop
1000000loops,bestof3:121nsperloop
x < y < z效率略高,而且可讀性更好。
51. while 1 比 while True 更快
defwhile_1():
n=100000
while1:
n-=1
ifn<=0:break
defwhile_true():
n=100000
whileTrue:
n-=1
ifn<=0:break
m,n=1000000,1000000
%timeit-n100while_1()
%timeit-n100while_true()
100loops,bestof3:3.69msperloop
100loops,bestof3:5.61msperloop
while 1 比 while true快很多,原因是在python2.x中,True是一個全局變量,而非關鍵字。
52. 使用**而不是pow
%timeit-n10000c=pow(2,20)
%timeit-n10000c=2**20
10000loops,bestof3:284nsperloop
10000loops,bestof3:16.9nsperloop
53. 使用 cProfile, cStringIO 和 cPickle等用c實現(xiàn)相同功能(分別對應profile, StringIO, pickle)的包
importcPickle
importpickle
a=range(10000)
%timeit-n100x=cPickle.dumps(a)
%timeit-n100x=pickle.dumps(a)
100loops,bestof3:1.58msperloop
100loops,bestof3:17msperloop
由c實現(xiàn)的包,速度快10倍以上!
54. 使用最佳的反序列化方式
下面比較了eval, cPickle, json方式三種對相應字符串反序列化的效率,可見json比cPickle快近3倍,比eval快20多倍。
importjson
importcPickle
a=range(10000)
s1=str(a)
s2=cPickle.dumps(a)
s3=json.dumps(a)
%timeit-n100x=eval(s1)
%timeit-n100x=cPickle.loads(s2)
%timeit-n100x=json.loads(s3)
100loops,bestof3:16.8msperloop
100loops,bestof3:2.02msperloop
100loops,bestof3:798μsperloop
55. 怎么才算精通python
這個問題比較難回答,我是看怎么樣才算是精通 Python這個知乎問答,按照自己的看法整理了一些觀點。不要問我是按什么標準整理的,我只能說,整理的這些點,第一,在我看來都說得不錯;第二,我自己都會去按照這些點來看看自己離 “精通” python還有多遠。
熟悉語法以及原聲數(shù)據(jù)結構
熟悉基本實現(xiàn)中的性能特點,就是知道什么操作會慢
會使用profile以及基于profile的性能分析工具
會使用運行時編譯和靜態(tài)編譯的工具。pypy,numba,cython,ctypes,original C/C++ extension
熟悉你所在領域的拓展庫,比如我,科學計算方面的庫不要太多,numpy衍生出來的一大堆大堆
了解基本的編譯過程,基本的操作系統(tǒng)知識(只要你C、C++學的還行就可以了)
要想精通python,寫的代碼首先得pythonic
研讀牛B的開源代碼,在這過程中會遇到python的許多高階用法
理解裝飾器,生成器,描述符,元類
掌握list comprehension,
多用內(nèi)置函數(shù):map,reduce,filter,iter,range,divmod,round,chr,enumerate,all,any,slice,zip+
56. python 猴子補丁相關
python里有一個很奇妙的monkey patch,中文叫做猴子補丁,是指的是在運行時動態(tài)替換某些已加載的模塊的實現(xiàn)。第一次了解這個概念是在使用gevent的時候,需要把python自帶的socket,os等相關模塊的實現(xiàn)改變成異步形式,但同時不改動python的源代碼。
57. 了解 functools.partial
先參考飄逸的python - 偏函數(shù)functools.partial
58. python 中時間格式轉換
59. python 一行代碼啟動 http 服務器
python -mSimpleHTTPServer