您好,登錄后才能下訂單哦!
1. 枚舉 - enumerate 可以有參數(shù)哦
之前我們這樣操作:
i = 0for item in iterable: print i, item i += 1
現(xiàn)在我們這樣操作:
for i, item in enumerate(iterable): print i, item
enumerate函數(shù)還可以接收第二個(gè)參數(shù)。就像下面這樣:
>>> list(enumerate('abc')) [(0, 'a'), (1, 'b'), (2, 'c')] >>> list(enumerate('abc', 1)) [(1, 'a'), (2, 'b'), (3, 'c')]
2. 字典/集合 解析
你也許知道如何進(jìn)行列表解析,但是可能不知道字典/集合解析。它們簡(jiǎn)單易用且高效。就像下面這個(gè)例子:
my_dict = {i: i * i for i in xrange(100)} my_set = {i * 15 for i in xrange(100)} # There is only a difference of ':' in both # 兩者的區(qū)別在于字典推導(dǎo)中有冒號(hào)
3. 強(qiáng)制浮點(diǎn)除法
from __future__ import division result = 1/2# print(result)# 0.5
4. 對(duì)Python表達(dá)式求值
我們都知道eval函數(shù),但是我們知道literal_eval函數(shù)么?也許很多人都不知道吧??梢杂眠@種操作:
import ast my_list = ast.literal_eval(expr)
來代替以下這種操作:
expr = "[1, 2, 3]"my_list = eval(expr)
我相信對(duì)于大多數(shù)人來說這種形式是第一次看見,但是實(shí)際上這個(gè)在Python中已經(jīng)存在很長(zhǎng)時(shí)間了。
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. 三元運(yùn)算
三元運(yùn)算是if-else 語(yǔ)句的快捷操作,也被稱為條件運(yùn)算。這里有幾個(gè)例子可以供你參考,它們可以讓你的代碼更加緊湊,更加美觀。
[on_true] if [expression] else [on_false] x, y = 50, 25small = x if x < y else y
7. Python里面如何拷貝一個(gè)對(duì)象
標(biāo)準(zhǔn)庫(kù)中的copy模塊提供了兩個(gè)方法來實(shí)現(xiàn)拷貝.一個(gè)方法是copy,它返回和參數(shù)包含內(nèi)容一樣的對(duì)象.
import copynew_list = copy.copy(existing_list)
有些時(shí)候,你希望對(duì)象中的屬性也被復(fù)制,可以使用deepcopy方法:
import copynew_list_of_dicts = copy.deepcopy(existing_list_of_dicts)copy(x)Shallow copy operation on arbitrary Python objects.deepcopy(x, memo=None, _nil=[]) Deep copy operation on arbitrary Python objects.
8. python中如何判斷對(duì)象相等
首先是C#中字符串的==和equal方法?!?=” :
對(duì)于內(nèi)置值類型而言, == 判斷兩個(gè)內(nèi)存值是否相等。
對(duì)于用戶自定義的值類型而言(Struct), == 需要重載,否則不能使用。
對(duì)于引用類型而言,默認(rèn)是同一引用才返回true,但是系統(tǒng)重載了很多引用類型的 == (比如下文提到的string),所以c#中引用類型的比較并不建議使用 ==?!癳quals” :
對(duì)于值類型而言, 內(nèi)存相等才返回true。
對(duì)于引用類型而言,指向同一個(gè)引用才算相等。
但是比較特殊的是字符串String,是一個(gè)特殊的引用型類型,在C#語(yǔ)言中,重載了string的equals()方法,使string對(duì)象用起來就像是值類型一樣。python中的 ==
python中的對(duì)象包含三要素:id, type, valueid 用來標(biāo)識(shí)唯一一個(gè)對(duì)象,type標(biāo)識(shí)對(duì)象的類型,value用來設(shè)置對(duì)象的值。is 判斷是否是一個(gè)對(duì)象,使用id來判斷的。
== 是判斷a對(duì)象的值是否是b對(duì)象的值,默認(rèn)調(diào)用它的__eq__方法。
9. 命名技巧
今天閱讀代碼,發(fā)現(xiàn)一個(gè)不錯(cuò)的函數(shù)命名方式:
def request(_argv):
就是把所有的參數(shù)前面都加上_下劃線,這樣你在函數(shù)體中,一眼就可以看出那些是局部變量,那些是作為參數(shù)傳入的,類似把全局變量前面加上g。
10. 開發(fā)者工具集錦
pydoc: 模塊可以根據(jù)源代碼中的docstrings為任何可導(dǎo)入模塊生成格式良好的文檔。
doctest模塊:該模塊可以從源代碼或獨(dú)立文件的例子中抽取出測(cè)試用例。
unittest模塊:該模塊是一個(gè)全功能的自動(dòng)化測(cè)試框架,該框架提供了對(duì)測(cè)試準(zhǔn)備(test fixtures), 預(yù)定義測(cè)試集(predefined test suite)以及測(cè)試發(fā)現(xiàn)(test discovery)的支持。
trace:模塊可以監(jiān)控Python執(zhí)行程序的方式,同時(shí)生成一個(gè)報(bào)表來顯示程序的每一行執(zhí)行的次數(shù)。這些信息可以用來發(fā)現(xiàn)未被自動(dòng)化測(cè)試集所覆蓋的程序執(zhí)行路徑,也可以用來研究程序調(diào)用圖,進(jìn)而發(fā)現(xiàn)模塊之間的依賴關(guān)系。編寫并執(zhí)行測(cè)試可以發(fā)現(xiàn)絕大多數(shù)程序中的問題,Python使得debug工作變得更加簡(jiǎn)單,這是因?yàn)樵诖蟛糠智闆r下,Python都能夠?qū)⑽幢惶幚淼腻e(cuò)誤打印到控制臺(tái)中,我們稱這些錯(cuò)誤信息為traceback。如果程序不是在文本控制臺(tái)中運(yùn)行的,traceback也能夠?qū)㈠e(cuò)誤信息輸出到日志文件或是消息對(duì)話框中。當(dāng)標(biāo)準(zhǔn)的traceback無法提供足夠的信息時(shí),可以使用cgitb 模塊來查看各級(jí)棧和源代碼上下文中的詳細(xì)信息,比如局部變量。cgitb模塊還能夠?qū)⑦@些跟蹤信息以HTML的形式輸出,用來報(bào)告web應(yīng)用中的錯(cuò)誤。
pdb:該模塊可以顯示出程序在錯(cuò)誤產(chǎn)生時(shí)的執(zhí)行路徑,同時(shí)可以動(dòng)態(tài)地調(diào)整對(duì)象和代碼進(jìn)行調(diào)試。
profile, timeit: 開發(fā)者可以使用profile以及timit模塊來測(cè)試程序的速度,找出程序中到底是哪里很慢,進(jìn)而對(duì)這部分代碼獨(dú)立出來進(jìn)行調(diào)優(yōu)的工作。
compileall: Python程序是通過解釋器執(zhí)行的,解釋器的輸入是原有程序的字節(jié)碼編譯版本。這個(gè)字節(jié)碼編譯版本可以在程序執(zhí)行時(shí)動(dòng)態(tài)地生成,也可以在程序打包的時(shí)候就生成。compileall模塊可以處理程序打包的事宜,它暴露出了打包相關(guān)的接口,該接口能夠被安裝程序和打包工具用來生成包含模塊字節(jié)碼的文件。同時(shí),在開發(fā)環(huán)境中,compileall模塊也可以用來驗(yàn)證源文件是否包含了語(yǔ)法錯(cuò)誤。
YAPF:Google開源的Python代碼格式化工具。
iPDB: iPDB是一個(gè)極好的工具,我已經(jīng)用它查出了很多匪夷所思的bug。pip install ipdb 安裝該工具,然后在你的代碼中import ipdb; ipdb.set_trace(),然后你會(huì)在你的程序運(yùn)行時(shí),獲得一個(gè)很好的交互式提示。它每次執(zhí)行程序的一行并且檢查變量。
pycallgraph: 在一些場(chǎng)合,我使用pycallgraph來追蹤性能問題。它可以創(chuàng)建函數(shù)調(diào)用時(shí)間和次數(shù)的圖表。
objgraph: objgraph對(duì)于查找內(nèi)存泄露非常有用。
11. Python代碼微優(yōu)化之加快查找
collections.OrderedDict類: def __setitem__(self, key, value, dict_setitem=dict.__setitem__): if key not in self: root = self.__root last = root[0] last[1] = root[0] = self.__map[key] = [last, root, key] return dict_setitem(self, key, value)
注意最后一個(gè)參數(shù):dict_setitem=dict.setitem。如果你仔細(xì)想就會(huì)感覺有道理。將值關(guān)聯(lián)到鍵上,你只需要給setitem傳遞三個(gè)參數(shù):要設(shè)置的鍵,與鍵關(guān)聯(lián)的值,傳遞給內(nèi)建dict類的setitem類方法。等會(huì),好吧,也許最后一個(gè)參數(shù)沒什么意義。 最后一個(gè)參數(shù)其實(shí)是將一個(gè)函數(shù)綁定到局部作用域中的一個(gè)函數(shù)上。具體是通過將dict.setitem賦值為參數(shù)的默認(rèn)值。這里還有另一個(gè)例子:
def not_list_or_dict(value): return not (isinstance(value, dict) or isinstance(value, list)) def not_list_or_dict(value, _isinstance=isinstance, _dict=dict, _list=list): return not (_isinstance(value, _dict) or _isinstance(value, _list))
這里我們做同樣的事情,把本來將會(huì)在內(nèi)建命名空間中的對(duì)象綁定到局部作用域中去。因此,python將會(huì)使用LOCAL_FAST而不是LOAD_GLOBAL(全局查找)。那么這到底有多快呢?我們做個(gè)簡(jiǎn)單的測(cè)試:
$ python -m timeit -s 'def not_list_or_dict(value): return not (isinstance(value, dict) or isinstance(value, list))' 'not_list_or_dict(50)'1000000 loops, best of 3: 0.48 usec per loop$ python -m timeit -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)'1000000 loops, best of 3: 0.423 usec per loop
換句話說,大概有11.9%的提升 [2]。比我在文章開始處承諾的5%還多!
12. 包管理
Python世界最棒的地方之一,就是大量的第三方程序包。同樣,管理這些包也非常容易。按照慣例,會(huì)在 requirements.txt 文件中列出項(xiàng)目所需要的包。每個(gè)包占一行,通常還包含版本號(hào)。
pelican==3.3Markdown pelican-extended-sitemap==1.0.0
13. Python函數(shù)參數(shù)默認(rèn)值的陷阱和原理深究
Python 2.7.9 (default, Dec 19 2014, 06:05:48) [GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.56)] on darwin Type "help", "copyright", "credits" or "license" for more information.>>> def generate_new_list_with(my_list=[], element=None): ... my_list.append(element) ... return my_list ...>>> list_1 = generate_new_list_with(element=1)>>> list_1 [1]>>> list_2 = generate_new_list_with(element=2)>>> list_2 [1, 2]>>>
可見代碼運(yùn)行結(jié)果并不和我們預(yù)期的一樣。list_2在函數(shù)的第二次調(diào)用時(shí)并沒有得到一個(gè)新的list并填入2,而是在第一次調(diào)用結(jié)果的基礎(chǔ)上append了一個(gè)2。為什么會(huì)發(fā)生這樣在其他編程語(yǔ)言中簡(jiǎn)直就是設(shè)計(jì)bug一樣的問題呢? 可見如果參數(shù)默認(rèn)值是在函數(shù)編譯compile階段就已經(jīng)被確定。之后所有的函數(shù)調(diào)用時(shí),如果參數(shù)不顯示的給予賦值,那么所謂的參數(shù)默認(rèn)值不過是一個(gè)指向那個(gè)在compile階段就已經(jīng)存在的對(duì)象的指針。如果調(diào)用函數(shù)時(shí),沒有顯示指定傳入?yún)?shù)值得話。那么所有這種情況下的該參數(shù)都會(huì)作為編譯時(shí)創(chuàng)建的那個(gè)對(duì)象的一種別名存在。如果參數(shù)的默認(rèn)值是一個(gè)不可變(Imuttable)數(shù)值,那么在函數(shù)體內(nèi)如果修改了該參數(shù),那么參數(shù)就會(huì)重新指向另一個(gè)新的不可變值。而如果參數(shù)默認(rèn)值是和本文最開始的舉例一樣,是一個(gè)可變對(duì)象(Muttable),那么情況就比較糟糕了。所有函數(shù)體內(nèi)對(duì)于該參數(shù)的修改,實(shí)際上都是對(duì)compile階段就已經(jīng)確定的那個(gè)對(duì)象的修改。
14. 單下劃線(_)
1、在解釋器中:在這種情況下,“_”代表交互式解釋器會(huì)話中上一條執(zhí)行的語(yǔ)句的結(jié)果。這種用法首先被標(biāo)準(zhǔn)CPython解釋器采用,然后其他類型的解釋器也先后采用。
>>> _ Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name '_' is not defined >>> 42>>> _ 42>>> 'alright!' if _ else ':(''alright!'>>> _ 'alright!'
2、作為一個(gè)名稱:這與上面一點(diǎn)稍微有些聯(lián)系,此時(shí)“”作為臨時(shí)性的名稱使用。這樣,當(dāng)其他人閱讀你的代碼時(shí)將會(huì)知道,你分配了一個(gè)特定的名稱,但是并不會(huì)在后面再次用到該名稱。例如,下面的例子中,你可能對(duì)循環(huán)計(jì)數(shù)中的實(shí)際值并不感興趣,此時(shí)就可以使用“”。
n = 42for _ in range(n): do_something()
3、國(guó)際化:也許你也曾看到”_“會(huì)被作為一個(gè)函數(shù)來使用。這種情況下,它通常用于實(shí)現(xiàn)國(guó)際化和本地化字符串之間翻譯查找的函數(shù)名稱,這似乎源自并遵循相應(yīng)的C約定。例如,在Django文檔“轉(zhuǎn)換”章節(jié)中,你將能看到如下代碼:
from django.utils.translation import ugettext as _ from django.http import HttpResponse def my_view(request): output = _("Welcome to my site.") return HttpResponse(output)
可以發(fā)現(xiàn),場(chǎng)景二和場(chǎng)景三中的使用方法可能會(huì)相互沖突,所以我們需要避免在使用“”作為國(guó)際化查找轉(zhuǎn)換功能的代碼塊中同時(shí)使用“”作為臨時(shí)名稱。
15. 名稱前的單下劃線(如:_shahriar)
程序員使用名稱前的單下劃線,用于指定該名稱屬性為“私有”。這有點(diǎn)類似于慣例,為了使其他人(或你自己)使用這些代碼時(shí)將會(huì)知道以“”開頭的名稱只供內(nèi)部使用。正如Python文檔中所述:
以下劃線 _ 為前綴的名稱(如pam)應(yīng)該被視為API中非公開的部分(不管是函數(shù)、方法還是數(shù)據(jù)成員)。此時(shí),應(yīng)該將它們看作是一種實(shí)現(xiàn)細(xì)節(jié),在修改它們時(shí)無需對(duì)外部通知。
正如上面所說,這確實(shí)類似一種慣例,因?yàn)樗鼘?duì)解釋器來說確實(shí)有一定的意義,如果你寫了代碼 : from <模塊/包名> import
,那么以 開頭的名稱都不會(huì)被導(dǎo)入,除非模塊或包中的 all列表顯式地包含了它們。了解更多請(qǐng)查看 Importing in Python
16. 名稱前的雙下劃線(如:__shahriar)
名稱(具體為一個(gè)方法名)前雙下劃線 _ 的用法并不是一種慣例,對(duì)解釋器來說它有特定的意義。Python中的這種用法是為了避免與子類定義的名稱沖突。Python文檔指出,spam 這種形式(至少兩個(gè)前導(dǎo)下劃線,最多一個(gè)后續(xù)下劃線)的任何標(biāo)識(shí)符將會(huì)被 正如所預(yù)料的,“_internal_use”并未改變,而“method_name”卻被變成了“_ClassName__method_name”。此時(shí),如果你創(chuàng)建A的一個(gè)子類B,那么你將不能輕易地覆寫A中的方法“__method_name”。spam 這種形式原文取代,在這里 classname 是去掉前導(dǎo)下劃線的當(dāng)前類名。例如下面的例子:
>>> class A(object): ... def _internal_use(self): ... pass... def __method_name(self): ... pass... >>> dir(A()) ['_A__method_name', ..., '_internal_use']
正如所預(yù)料的,“_internal_use”并未改變,而“method_name”卻被變成了“_ClassNamemethod_name”。此時(shí),如果你創(chuàng)建A的一個(gè)子類B,那么你將不能輕易地覆寫A中的方法“__method_name”。
17. 名稱前后的雙下劃線(如:init)
這種用法表示Python中特殊的方法名。其實(shí),這只是一種慣例,對(duì)Python系統(tǒng)來說,這將確保不會(huì)與用戶自定義的名稱沖突。通常,你將會(huì)覆寫這些方法,并在里面實(shí)現(xiàn)你所需要的功能,以便Python調(diào)用它們。例如,當(dāng)定義一個(gè)類時(shí),你經(jīng)常會(huì)覆寫“init”方法。
雖然你也可以編寫自己的特殊方法名,但不要這樣做。
17. 隱藏特性 1,函數(shù)unpack
def foo(x, y): print x, y alist = [1, 2] adict = {'x': 1, 'y': 2} foo(*alist) # 1, 2foo(**adict) # 1, 2
18. 隱藏特性 2, 鏈?zhǔn)奖容^操作符
>>> x = 3>>> 1 < x < 5True>>> 4 > x >=3True
19. 隱藏特性 3,函數(shù)的默認(rèn)參數(shù)
>>> def foo(x=[]): ... x.append(1) ... print x ...>>> foo() [1]>>> foo() [1, 1]
更安全的做法是:
>>> def foo(x=None):... if x is None:... x = []... x.append(1)... print x ...>>> foo() [1]>>> foo() [1] >>>
20. 隱藏特性 4,字典的get方法
21. 隱藏特性 5,帶關(guān)鍵字的格式化
>>> print "Hello %(name)s !" % {'name': 'James'} Hello James !>>> print "I am years %(age)i years old" % {'age': 18} I am years 18 years old
更新些的格式化:
>>> print "Hello {name} !".format(name="James") Hello James !
22. 隱藏特性 6,切片操作的步長(zhǎng)參數(shù)
可以用步長(zhǎng) -1 來反轉(zhuǎn)鏈表:>>> a = [1, 2, 3, 4, 5]>>> a[::2] [1, 3, 5]>>> a[::-1] [5, 4, 3, 2, 1]>>>
23. 隱藏特性 7,嵌套列表推導(dǎo)式
[(i, j) for i in range(3) for j in range(i)] [(1, 0), (2, 0), (2, 1)]
列表推導(dǎo)構(gòu)造permutation:可以用 itertools.permutations 來實(shí)現(xiàn)。
In[47]: a = 'abcd'In[48]: [i+j+k for i in a for j in a.replace(i,'') for k in a.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” , 當(dāng)然 “a” 是可以的
>>> print >> open("somefile", "w+"), "Hello World"
25. 隱藏特性 9, Python3中的元組unpack
>>> a, b, *rest = range(10) >>> a0>>> b1>>> rest [2, 3, 4, 5, 6, 7, 8, 9] >>> >>> first, second, *rest, last = range(10) >>> first0>>> second1>>> last9>>> rest [2, 3, 4, 5, 6, 7, 8]
26. 隱藏特性 10,pow的第三個(gè)參數(shù)
其實(shí)第三個(gè)參數(shù)是來求模的: pow(x, y, z) == (x ** y) % z,注意,內(nèi)置的 pow 和 math.pow 并不是一個(gè)函數(shù),后者只接受2個(gè)參數(shù)。
>>> pow(4, 2, 2)0>>> pow(4, 2, 3)1
27. 隱藏特性 11,enumerate還有第二個(gè)參數(shù)
enumerate 很贊,可以給我們索引和序列值的對(duì), 但是它還有第二個(gè)參數(shù),這個(gè)參數(shù)用來: 指明索引的起始值。
>>> lst = ["a", "b", "c"]>>> list(enumerate(lst, 1)) [(1, 'a'), (2, 'b'), (3, 'c')]
28. 隱藏特性 12,顯式的聲明一個(gè)集合
在Python 2.7 之后可以這么聲明一個(gè)集合。
>>> {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]
當(dāng)然用 del a[1:4] 也是可以的,去除偶數(shù)項(xiàng)(偶數(shù)索引的):
>>> a = [0, 1, 2, 3, 4, 5, 6, 7] >>> del a[::2] >>> a[1, 3, 5, 7]
30. 隱藏特性 14,isinstance可以接收一個(gè)元組
這個(gè)真的鮮為人知, 我們可以用 isinstance(x, (float, int)) 來判斷 x 是不是數(shù),也就是那個(gè)元組里面是 或 的關(guān)系,只要是其中一個(gè)的實(shí)例就返回 True。
>>> isinstance(1, (float, int))True>>> isinstance(1.3, (float, int))True>>> isinstance("1.3", (float, int))False
31. 讓關(guān)鍵代碼依賴于外部包
雖然Python讓許多編程任務(wù)變得容易,但它可能并不總能為緊急的任務(wù)提供最佳性能。你可以為緊急的任務(wù)使用C、C++或機(jī)器語(yǔ)言編寫的外部包,這樣可以提高應(yīng)用程序的性能。這些包都是不能跨平臺(tái)的,這意味著你需要根據(jù)你正在使用的平臺(tái),尋找合適的包。簡(jiǎn)而言之,這個(gè)方案放棄了一些應(yīng)用程序的可移植性,以換取只有在特定主機(jī)上直接編程才能獲得的程序性能。這里有一些你應(yīng)該考慮加入到你的“性能兵工廠”的包:
這些包以不同的方式提高性能。例如,Pyrex能夠擴(kuò)展Python所能做的事情,例如使用C的數(shù)據(jù)類型來讓內(nèi)存任務(wù)更加有效或直接。PyInIne讓你在Python應(yīng)用程序中直接使用C代碼。程序中的內(nèi)聯(lián)代碼單獨(dú)編譯,但它在利用C語(yǔ)言所能提供的效率的同時(shí),也讓所有的代碼都在同一個(gè)地方。
32. 排序時(shí)使用鍵(key)
有很多老的Python排序代碼,它們?cè)谀銊?chuàng)建一個(gè)自定義的排序時(shí)花費(fèi)你的時(shí)間,但在運(yùn)行時(shí)確實(shí)能加速執(zhí)行排序過程。元素排序的最好方法是盡可能使用鍵(key)和默認(rèn)的sort()排序方法。例如,考慮下面的代碼:
import operator 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
每一個(gè)實(shí)例中,根據(jù)你選擇的作為key參數(shù)部分的索引,數(shù)組進(jìn)行了排序。類似于利用數(shù)字進(jìn)行排序,這種方法同樣適用于利用字符串排序。
33. 優(yōu)化循環(huán)
每種編程語(yǔ)言都會(huì)強(qiáng)調(diào)需要優(yōu)化循環(huán)。當(dāng)使用Python的時(shí)候,你可以依靠大量的技巧使得循環(huán)運(yùn)行得更快。然而,開發(fā)者經(jīng)常漏掉的一個(gè)方法是:避免在一個(gè)循環(huán)中使用點(diǎn)操作。例如,考慮下面的代碼:
lowerlist = ['this', 'is', 'lowercase'] upper = str.upperupperlist = [] append = upperlist.appendfor word in lowerlist: append(upper(word)) print(upperlist) #Output = ['THIS', 'IS', 'LOWERCASE']
每一次你調(diào)用方法str.upper,Python都會(huì)求該方法的值。然而,如果你用一個(gè)變量代替求得的值,值就變成了已知的,Python就可以更快地執(zhí)行任務(wù)。優(yōu)化循環(huán)的關(guān)鍵,是要減少Python在循環(huán)內(nèi)部執(zhí)行的工作量,因?yàn)镻ython原生的解釋器在那種情況下,真的會(huì)減緩執(zhí)行的速度。
(注意:優(yōu)化循環(huán)的方法有很多,這只是其中的一個(gè)。例如,許多程序員都會(huì)說,列表推導(dǎo)是在循環(huán)中提高執(zhí)行速度的最好方式。這里的關(guān)鍵是,優(yōu)化循環(huán)是程序取得更高的執(zhí)行速度的更好方式之一。)
34. 嘗試多種編碼方法
如果每次你創(chuàng)建一個(gè)應(yīng)用程序都是用相同的編碼方法,幾乎肯定會(huì)導(dǎo)致一些你的應(yīng)用程序比它能夠達(dá)到的運(yùn)行效率慢的情況。作為分析過程的一部分,你可以嘗試一些實(shí)驗(yàn)。例如,在一個(gè)字典中管理一些元素,你可以采用安全的方法確定元素是否已經(jīng)存在并更新,或者你可以直接添加元素,然后作為異常處理該元素不存在情況??紤]第一個(gè)編碼的例子:
n = 16myDict = {}for i in range(0, n): char = 'abcd'[i%4] if char not in myDict: myDict[char] = 0 myDict[char] += 1 print(myDict)
這段代碼通常會(huì)在myDict開始為空時(shí)運(yùn)行得更快。然而,當(dāng)mydict通常被數(shù)據(jù)填充(或者至少大部分被充填)時(shí),另一種方法效果更好。
n = 16myDict = {}for i in range(0, n): char = 'abcd'[i%4] try: myDict[char] += 1 except KeyError: myDict[char] = 1 print(myDict)
兩種情況下具有相同的輸出:{‘d': 4, ‘c': 4, ‘b': 4, ‘a(chǎn)': 4}。唯一的不同是這個(gè)輸出是如何得到的。跳出固定的思維模式,創(chuàng)造新的編碼技巧,能夠幫助你利用你的應(yīng)用程序獲得更快的結(jié)果。
35. 使用列表推導(dǎo)式
一個(gè)列表推導(dǎo)式包含以下幾個(gè)部分:
一個(gè)輸入序列
一個(gè)表示輸入序列成員的變量
一個(gè)可選的斷言表達(dá)式
一個(gè)將輸入序列中滿足斷言表達(dá)式的成員變換成輸出列表成員的輸出表達(dá)式
num = [1, 4, -5, 10, -7, 2, 3, -1] filtered_and_squared = []for number in num: if number > 0: filtered_and_squared.append(number ** 2) print filtered_and_squared# [1, 16, 100, 4, 9]
而如果使用filter、lambda和map函數(shù),則能夠?qū)⒋a大大簡(jiǎn)化:
num = [1, 4, -5, 10, -7, 2, 3, -1] filtered_and_squared = map(lambda x: x ** 2, filter(lambda x: x > 0, num))print filtered_and_squared # [1, 16, 100, 4, 9] ## 更簡(jiǎn)化的一種寫法 num = [1, 4, -5, 10, -7, 2, 3, -1] filtered_and_squared = [ x**2 for x in num if x > 0]print filtered_and_squared # [1, 16, 100, 4, 9]
列表推導(dǎo)也可能會(huì)有一些負(fù)面效應(yīng),那就是整個(gè)列表必須一次性加載于內(nèi)存之中,這對(duì)上面舉的例子而言不是問題,甚至擴(kuò)大若干倍之后也都不是問題。但是總會(huì)達(dá)到極限,內(nèi)存總會(huì)被用完。
針對(duì)上面的問題,生成器(Generator)能夠很好的解決。生成器表達(dá)式不會(huì)一次將整個(gè)列表加載到內(nèi)存之中,而是生成一個(gè)生成器對(duì)象(Generator objector),所以一次只加載一個(gè)列表元素。
生成器表達(dá)式同列表推導(dǎo)式有著幾乎相同的語(yǔ)法結(jié)構(gòu),區(qū)別在于生成器表達(dá)式是被圓括號(hào)包圍,而不是方括號(hào):
num = [1, 4, -5, 10, -7, 2, 3, -1] filtered_and_squared = ( x**2 for x in num if x > 0 ) print filtered_and_squared# <generator object <genexpr> at 0x00583E18>for item in filtered_and_squared: print item# 1, 16, 100 4,9
這比列表推導(dǎo)效率稍微提高一些,讓我們?cè)僖淮胃脑煲幌麓a:
num = [1, 4, -5, 10, -7, 2, 3, -1]def square_generator(optional_parameter): return (x ** 2 for x in num if x > optional_parameter)print square_generator(0)# <generator object <genexpr> at 0x004E6418># Option Ifor k in square_generator(0): print k# 1, 16, 100, 4, 9# Option IIg = list(square_generator(0))print g# [1, 16, 100, 4, 9]
除非特殊的原因,應(yīng)該經(jīng)常在代碼中使用生成器表達(dá)式。但除非是面對(duì)非常大的列表,否則是不會(huì)看出明顯區(qū)別的。 再來看一個(gè)通過兩階列表推導(dǎo)式遍歷目錄的例子:
import os def tree(top): for path, names, fnames in os.walk(top): for fname in fnames: yield os.path.join(path, fname)for name in tree('C:\Users\XXX\Downloads\Test'): print name
36. 裝飾器(Decorators)
裝飾器為我們提供了一個(gè)增加已有函數(shù)或類的功能的有效方法。聽起來是不是很像Java中的面向切面編程(Aspect-Oriented Programming)概念??jī)烧叨己芎?jiǎn)單,并且裝飾器有著更為強(qiáng)大的功能。舉個(gè)例子,假定你希望在一個(gè)函數(shù)的入口和退出點(diǎn)做一些特別的操作(比如一些安全、追蹤以及鎖定等操作)就可以使用裝飾器。
裝飾器是一個(gè)包裝了另一個(gè)函數(shù)的特殊函數(shù):主函數(shù)被調(diào)用,并且其返回值將會(huì)被傳給裝飾器,接下來裝飾器將返回一個(gè)包裝了主函數(shù)的替代函數(shù),程序的其他部分看到的將是這個(gè)包裝函數(shù)。
import timefrom functools import wrapsdef timethis(func): ''' Decorator that reports the execution time. ''' @wraps(func) def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(func.__name__, end-start) return result return wrapper@timethisdef countdown(n): while n > 0: n -= 1countdown(100000)# ('countdown', 0.006999969482421875)
37. 上下文管理庫(kù)(ContextLib)
contextlib模塊包含了與上下文管理器和with聲明相關(guān)的工具。通常如果你想寫一個(gè)上下文管理器,則你需要定義一個(gè)類包含enter方法以及exit方法,例如:
import timeclass demo: 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))
完整的例子在此:
import timeclass demo: 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)) with demo('counting'): n = 10000000 while n > 0: n -= 1# counting: 1.36000013351
上下文管理器被with聲明所激活,這個(gè)API涉及到兩個(gè)方法。 enter方法,當(dāng)執(zhí)行流進(jìn)入with代碼塊時(shí),enter方法將執(zhí)行。并且它將返回一個(gè)可供上下文使用的對(duì)象。
當(dāng)執(zhí)行流離開with代碼塊時(shí),exit方法被調(diào)用,它將清理被使用的資源。
利用@contextmanager裝飾器改寫上面那個(gè)例子:
from contextlib import contextmanagerimport time@contextmanagerdef demo(label): start = time.time() try: yield finally: end = time.time() print('{}: {}'.format(label, end - start))with demo('counting'): n = 10000000 while n > 0: n -= 1# counting: 1.32399988174
看上面這個(gè)例子,函數(shù)中yield之前的所有代碼都類似于上下文管理器中enter方法的內(nèi)容。而yield之后的所有代碼都如exit方法的內(nèi)容。如果執(zhí)行過程中發(fā)生了異常,則會(huì)在yield語(yǔ)句觸發(fā)。
38. 描述器(Descriptors)
描述器決定了對(duì)象屬性是如何被訪問的。描述器的作用是定制當(dāng)你想引用一個(gè)屬性時(shí)所發(fā)生的操作。
構(gòu)建描述器的方法是至少定義以下三個(gè)方法中的一個(gè)。需要注意,下文中的instance是包含被訪問屬性的對(duì)象實(shí)例,而owner則是被描述器修辭的類。
get(self, instance, owner) – 這個(gè)方法是當(dāng)屬性被通過(value = obj.attr)的方式獲取時(shí)調(diào)用,這個(gè)方法的返回值將被賦給請(qǐng)求此屬性值的代碼部分。 set(self, instance, value) – 這個(gè)方法是當(dāng)希望設(shè)置屬性的值(obj.attr = ‘value')時(shí)被調(diào)用,該方法不會(huì)返回任何值。 delete(self, instance) – 當(dāng)從一個(gè)對(duì)象中刪除一個(gè)屬性時(shí)(del obj.attr),調(diào)用此方法。 譯者注:對(duì)于instance和owner的理解,考慮以下代碼:
class Celsius(object): def __init__(self, value=0.0): self.value = float(value) def __get__(self, instance, owner): return self.value def __set__(self, instance, value): self.value = float(value)class Temperature(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 = lambda a, 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>>> from itertools import islice>>> group_adjacent = lambda a, k: zip(*(islice(a, i, None, k) for i in range(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
>>> from itertools import islice>>> def n_grams(a, n):... z = (islice(a, i, None) for i in range(n))... return zip(*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] >>> [x for l in a for x in l] [1, 2, 3, 4, 5, 6] >>> a = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] >>> [x for l1 in a for l2 in l1 for x in l2] [1, 2, 3, 4, 5, 6, 7, 8] >>> a = [1, 2, [3, 4], [[5, 6], [7, 8]]] >>> flatten = lambda x: [y for l in x for y in flatten(l)] if type(x) is list else [x] >>> flatten(a) [1, 2, 3, 4, 5, 6, 7, 8]
44. Dictionary comprehensions
>>> m = {x: x ** 2 for x in range(5)}>>> m {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}>>> m = {x: 'A' + str(x) for x in range(10)}>>> m {0: 'A0', 1: 'A1', 2: 'A2', 3: 'A3', 4: 'A4', 5: 'A5', 6: 'A6', 7: 'A7', 8: 'A8', 9: 'A9'}
45. 常犯錯(cuò)誤,濫用表達(dá)式作為函數(shù)參數(shù)默認(rèn)值
Python允許開發(fā)者指定一個(gè)默認(rèn)值給函數(shù)參數(shù),雖然這是該語(yǔ)言的一個(gè)特征,但當(dāng)參數(shù)可變時(shí),很容易導(dǎo)致混亂,例如,下面這段函數(shù)定義:
>>> def foo(bar=[]): # bar is optional and defaults to [] if not specified... bar.append("baz") # but this line could be problematic, as we'll see...... return bar
在上面這段代碼里,一旦重復(fù)調(diào)用foo()函數(shù)(沒有指定一個(gè)bar參數(shù)),那么將一直返回'bar',因?yàn)闆]有指定參數(shù),那么foo()每次被調(diào)用的時(shí)候,都會(huì)賦予[]。下面來看看,這樣做的結(jié)果:
>>> foo() ["baz"]>>> foo() ["baz", "baz"]>>> foo() ["baz", "baz", "baz"]
解決方案:
>>> def foo(bar=None):... if bar is None: # or if not bar:... bar = []... bar.append("baz")... return bar ...>>> foo() ["baz"]>>> foo() ["baz"]>>> foo() ["baz"]
46. 誤解Python規(guī)則范圍
Python的作用域解析是基于LEGB規(guī)則,分別是Local、Enclosing、Global、Built-in。實(shí)際上,這種解析方法也有一些玄機(jī),看下面這個(gè)例子:
>>> x = 10>>> def foo(): ... x += 1... print x ...>>> foo() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in fooUnboundLocalError: local variable 'x' referenced before assignment
許多人會(huì)感動(dòng)驚訝,當(dāng)他們?cè)诠ぷ鞯暮瘮?shù)體里添加一個(gè)參數(shù)語(yǔ)句,會(huì)在先前工作的代碼里報(bào)UnboundLocalError錯(cuò)誤( 點(diǎn)擊這里查看更詳細(xì)描述)。 在使用列表時(shí),開發(fā)者是很容易犯這種錯(cuò)誤的,看看下面這個(gè)例子:
>>> lst = [1, 2, 3]>>> def foo1(): ... lst.append(5) # This works ok......>>> foo1()>>> lst [1, 2, 3, 5]>>> lst = [1, 2, 3]>>> def foo2(): ... lst += [5] # ... but this bombs!...>>> foo2() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in fooUnboundLocalError: local variable 'lst' referenced before assignment
為什么foo2失敗而foo1運(yùn)行正常? 答案與前面那個(gè)例子是一樣的,但又有一些微妙之處。foo1沒有賦值給lst,而foo2賦值了。lst += [5]實(shí)際上就是lst = lst + [5],試圖給lst賦值(因此,假設(shè)Python是在局部作用域里)。然而,我們正在尋找指定給lst的值是基于lst本身,其實(shí)尚未確定。
47. 修改遍歷列表
>>> odd = lambda x : bool(x % 2) >>> numbers = [n for n in range(10)] >>> for i in range(len(numbers)): ... if odd(numbers[i]): ... del numbers[i] # BAD: Deleting item from a list while iterating over it ... Traceback (most recent call last): File "<stdin>", line 2, in <module> IndexError: list index out of range
在遍歷的時(shí)候,對(duì)列表進(jìn)行刪除操作,這是很低級(jí)的錯(cuò)誤。稍微有點(diǎn)經(jīng)驗(yàn)的人都不會(huì)犯。 對(duì)上面的代碼進(jìn)行修改,正確地執(zhí)行:
>>> odd = lambda x : bool(x % 2)>>> numbers = [n for n in range(10)]>>> numbers[:] = [n for n in numbers if not odd(n)] # ahh, the beauty of it all>>> numbers [0, 2, 4, 6, 8]
48. 合理使用copy與deepcopy
對(duì)于dict和list等數(shù)據(jù)結(jié)構(gòu)的對(duì)象,直接賦值使用的是引用的方式。而有些情況下需要復(fù)制整個(gè)對(duì)象,這時(shí)可以使用copy包里的copy和deepcopy,這兩個(gè)函數(shù)的不同之處在于后者是遞歸復(fù)制的。效率也不一樣:(以下程序在ipython中運(yùn)行)
timeit后面的-n表示運(yùn)行的次數(shù),后兩行對(duì)應(yīng)的是兩個(gè)timeit的輸出,下同。由此可見后者慢一個(gè)數(shù)量級(jí)。
import copya = range(100000) %timeit -n 10 copy.copy(a) # 運(yùn)行10次 copy.copy(a)%timeit -n 10 copy.deepcopy(a)10 loops, best of 3: 1.55 ms per loop10 loops, best of 3: 151 ms per loop
49. 合理使用生成器(generator)和yield
%timeit -n 100 a = (i for i in range(100000)) %timeit -n 100 b = [i for i in range(100000)]100 loops, best of 3: 1.54 ms per loop100 loops, best of 3: 4.56 ms per loop
使用()得到的是一個(gè)generator對(duì)象,所需要的內(nèi)存空間與列表的大小無關(guān),所以效率會(huì)高一些。在具體應(yīng)用上,比如set(i for i in range(100000))會(huì)比set([i for i in range(100000)])快。
但是對(duì)于需要循環(huán)遍歷的情況:
%timeit -n 10 for x in (i for i in range(100000)): pass %timeit -n 10 for x in [i for i in range(100000)]: pass10 loops, best of 3: 6.51 ms per loop10 loops, best of 3: 5.54 ms per loop
后者的效率反而更高,但是如果循環(huán)里有break,用generator的好處是顯而易見的。yield也是用于創(chuàng)建generator:
50. 使用級(jí)聯(lián)比較x < y < z
x, y, z = 1,2,3%timeit -n 1000000 if x < y < z:pass %timeit -n 1000000 if x < y and y < z:pass1000000 loops, best of 3: 101 ns per loop1000000 loops, best of 3: 121 ns per loop
x < y < z效率略高,而且可讀性更好。
51. while 1 比 while True 更快
def while_1(): n = 100000 while 1: n -= 1 if n <= 0: breakdef while_true(): n = 100000 while True: n -= 1 if n <= 0: break m, n = 1000000, 1000000 %timeit -n 100 while_1() %timeit -n 100 while_true()100 loops, best of 3: 3.69 ms per loop100 loops, best of 3: 5.61 ms per loop
while 1 比 while true快很多,原因是在python2.x中,True是一個(gè)全局變量,而非關(guān)鍵字。
52. 使用**而不是pow
%timeit -n 10000 c = pow(2,20) %timeit -n 10000 c = 2**2010000 loops, best of 3: 284 ns per loop10000 loops, best of 3: 16.9 ns per loop
53. 使用 cProfile, cStringIO 和 cPickle等用c實(shí)現(xiàn)相同功能(分別對(duì)應(yīng)profile, StringIO, pickle)的包
import cPickleimport pickle a = range(10000) %timeit -n 100 x = cPickle.dumps(a) %timeit -n 100 x = pickle.dumps(a)100 loops, best of 3: 1.58 ms per loop100 loops, best of 3: 17 ms per loop
由c實(shí)現(xiàn)的包,速度快10倍以上!
54. 使用最佳的反序列化方式
下面比較了eval, cPickle, json方式三種對(duì)相應(yīng)字符串反序列化的效率,可見json比cPickle快近3倍,比eval快20多倍。
import jsonimport cPickle a = range(10000)s1 = str(a)s2 = cPickle.dumps(a)s3 = json.dumps(a)%timeit -n 100 x = eval(s1) %timeit -n 100 x = cPickle.loads(s2) %timeit -n 100 x = json.loads(s3)100 loops, best of 3: 16.8 ms per loop100 loops, best of 3: 2.02 ms per loop100 loops, best of 3: 798 μs per loop
55. 怎么才算精通python
這個(gè)問題比較難回答,按照自己的看法整理了一些觀點(diǎn)。不要問我是按什么標(biāo)準(zhǔn)整理的,我只能說,整理的這些點(diǎn),第一,在我看來都說得不錯(cuò);第二,我自己都會(huì)去按照這些點(diǎn)來看看自己離 “精通” python還有多遠(yuǎn)。
56. python 猴子補(bǔ)丁相關(guān)
python里有一個(gè)很奇妙的monkey patch,中文叫做猴子補(bǔ)丁,是指的是在運(yùn)行時(shí)動(dòng)態(tài)替換某些已加載的模塊的實(shí)現(xiàn)。第一次了解這個(gè)概念是在使用gevent的時(shí)候,需要把python自帶的socket,os等相關(guān)模塊的實(shí)現(xiàn)改變成異步形式,但同時(shí)不改動(dòng)python的源代碼。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。