如何像Python高手(Pythonista)一樣程式設計
最近在網上看到一篇介紹Pythonic程式設計的文章:Code Like a Pythonista: Idiomatic Python,其實作者在2006的PyCon會議後就寫了這篇文章,寫這篇文章的主要原因是作者發現很多有經驗的Pythoner寫出的程式碼不夠Pythonic。我覺得這篇文章很不錯,所以將它用中文寫了下來(不是逐字的翻譯,中間加了一些自己的理解),分享給大家。另:由於本人平時時間有限,這篇文章翻譯了比較長的時間,如果你發現了什麼不對的地方,歡迎指出。。
一、Python之禪(The Zen of Python)
The Zen of Python是Python語言的指導原則,遵循這些基本原則,你就可以像個Pythonista一樣程式設計。具體內容你可以在Python命令列輸入import this看到:
The Zen of Python, by Tim Peters Beautiful is better than ugly. # 優美勝於醜陋(Python以編寫優美的程式碼為目標) Explicit is better than implicit. # 明瞭勝於晦澀(優美的程式碼應當是明瞭的,命名規範,風格相似) Simple is better than complex. # 簡潔勝於複雜(優美的程式碼應當是簡潔的,不要有複雜的內部實現) Complex is better than complicated. # 複雜勝於凌亂(如果複雜不可避免,那程式碼間也不能有難懂的關係,要保持介面簡潔) Flat is better than nested. # 扁平勝於巢狀(優美的程式碼應當是扁平的,不能有太多的巢狀) Sparse is better than dense. # 間隔勝於緊湊(優美的程式碼有適當的間隔,不要奢望一行程式碼解決問題) Readability counts. # 可讀性很重要(優美的程式碼是可讀的) Special cases aren't special enough to break the rules. Although practicality beats purity. # 即便假借特例的實用性之名,也不可違背這些規則(這些規則至高無上) Errors should never pass silently. Unless explicitly silenced. # 不要包容所有錯誤,除非你確定需要這樣做(精準地捕獲異常,不寫except:pass風格的程式碼) In the face of ambiguity, refuse the temptation to guess. # 當存在多種可能,不要嘗試去猜測 There should be one-- and preferably only one --obvious way to do it. # 而是儘量找一種,最好是唯一一種明顯的解決方案(如果不確定,就用窮舉法) Although that way may not be obvious at first unless you're Dutch. # 雖然這並不容易,因為你不是 Python 之父(這裡的Dutch是指Guido) Now is better than never. Although never is often better than *right* now. # 做也許好過不做,但不假思索就動手還不如不做(動手之前要細思量) If the implementation is hard to explain, it's a bad idea. If the implementation is easy to explain, it may be a good idea. # 如果你無法向人描述你的方案,那肯定不是一個好方案;反之亦然(方案測評標準) Namespaces are one honking great idea -- let's do more of those! # 名稱空間是一種絕妙的理念,我們應當多加利用(倡導與號召)
這首特別的“詩”開始作為一個笑話,但它確實包含了很多關於Python背後的哲學真理。Python之禪已經正式成文PEP 20,具體內容見:PEP 20
二、PEP8: Python編碼規範(PEP8: Style Guide for Python Code)
Abelson & Sussman在《計算機程式的構造和解釋》一書中說道:程式是寫來給人讀的,只是順帶讓機器執行。所以,我們在編碼時應該儘量讓它更易讀懂。PEP8是Python的編碼規範,官方文件見:PEP 8,PEP是Python Enhancement Proposal的縮寫。PEP8包括很多編碼的規範,下面主要介紹一下縮排和命名等內容。
空格和縮排(WhiteSpace and Indentation)
空格和縮排在Python語言中非常重要,它替代了其他語言中{}的作用,用來區分程式碼塊和作用域。在這方面PEP8有以下的建議:
1、每次縮排使用4個空格 2、不要使用Tab,更不要Tab和空格混用 3、兩個方法之間使用一個空行,兩個Class之間使用兩個空行 4、新增一個空格在字典、列表、序列、引數列表中的“,“後,以及在字典中的”:“之後,而不是之前 5、在賦值和比較兩邊放置一個空格(引數列表中除外) 6、緊隨括號後面或者引數列表前一個字元不要存在空格
Python命名
命名規範是程式語言的基礎,而且大部分的規範對於高階語言來說都是一樣的,Python的基本規範如下:
1、方法 & 屬性:joined_lower 2、常量:joined_lower or ALL_CAPS 3、類:StudlyCaps 4、類屬性:interface, _internal, __private 5、camelCase only to conform to pre-existing conventions
以上內容只是對PEP8做了非常簡單的介紹,由於今天的主題不在於此,所以就不在這裡多講。想要更加深入的瞭解Python編碼規範,可以閱讀PEP8官方文件和Google Python編碼規範等內容。
三、交換變數值(Swap Values)
在其他語言中,交換兩個變數值的時候,可以這樣寫:
temp = a a = b b = temp
在Python中,我們可以簡單的這樣寫:
b, a = a, b
可能你已經在其他地方見過這種寫法,但是你知道Python是如何實現這種語法的嗎?首先,逗號(,)是Python中tuple資料結構的語法;上面的語法會執行一下的操作:
1、Python會先將右邊的a, b生成一個tuple(元組),存放在記憶體中;
2、之後會執行賦值操作,這時候會將tuple拆開;
3、然後將tuple的第一個元素賦值給左邊的第一個變數,第二個元素賦值給左邊第二個變數。
再舉個tuple拆分的例子:
In [1]: people = ['David', 'Pythonista', '15145551234'] In [2]: name, title, phone = people In [3]: name Out[3]: 'David' In [4]: title Out[4]: 'Pythonista' In [5]: phone Out[5]: '15145551234'
這種語法在For迴圈中非常實用:
In [6]: people = [['David', 'Pythonista', '15145551234'], ['Wu', 'Student', '15101365547']] In [7]: for name, title, phone in people: ...: print name, phone ...: David 15145551234 Wu 15101365547
PS:在使用這種語法時,需要確保左邊的變數個數和右邊tuple的個數一致,否則,Python會丟擲ValueError異常。
更多tuple的例子:
>>> 1, (1,) >>> (1,) (1,) >>> (1) 1 >>> value = 1, >>> value (1,)
我們知道:逗號(,)在Python中是建立tuple的構造器,所以我們可以按照上面的方式很方便的建立一個tuple;需要注意的是:如果宣告只有一個元素的tuple,末尾必須要帶上逗號,兩個以上的元素則不需要。宣告tuple的語法很簡單,但同時它也比較坑:如果你發現Python中的變數不可思議的變成了tuple,那很可能是因為你多寫了一個逗號。。
四、Python控制檯的"_"(Interactive "_")
這是Python中比較有用的一個功能,不過有很多人不知道(我也是接觸Python很久之後才知道的)。。在Python的互動式控制檯中,當你計算一個表示式或者呼叫一個方法的時候,運算的結果都會放在一個臨時的變數 _ 裡面。_(下劃線)用來儲存上一次的列印結果,比如:
>>> import math >>> math.pi / 3 1.0471975511965976 >>> angle = _ >>> math.cos(angle) 0.50000000000000011 >>> _ 0.50000000000000011
PS:當返回結果為None的時候,控制檯不會列印,_ 裡面儲存的值也就不會改變。
五、合併字串(Building Strings from Sub strings)
假如現在有一個list,裡面是一些字串,你現在需要將它們合併成一個字串,最簡單的方法,你可以按照下面的方式去處理:
colors = ['red', 'blue', 'green', 'yellow'] result = '' for s in colors: result += s
但是,很快你會發現:這種方法非常低效,尤其當list非常大的時候。Python中的字串物件是不可改變的,因此對任何字串的操作如拼接,修改等都將產生一個新的字串物件,而不是基於原字串。所以,上面的方法會消耗很大的記憶體:它需要計算,儲存,同時扔掉中間的計算結果。正確的方法是使用Python中的join方法:
result = ','.join(colors)
當合並元素比較少的時候,使用join方法看不出太大的效果;但是當元素多的時候,你會發現join的效率還是非常明顯的。不過,在使用的時候請注意:join只能用於元素是字串的list,它不會進行任何的強制型別轉換。連線一個存在一個或多個非字串元素的list時將丟擲異常。
六、使用關鍵字in(Use in where possible)
當你需要判斷一個KEY是否在dict中或者要遍歷dict的KEY時,最好的方法是使用關鍵字in:
d = {'a': 1, 'b': 2} if 'c' in d: print True # DO NOT USE if d.has_key('c'): print True for key in d: print key # DO NOT USE for key in d.keys(): print key
Python的dict物件是對KEY做過hash的,而keys()方法會將dict中所有的KEY作為一個list物件;所以,直接使用in的時候執行效率會比較快,程式碼也更簡潔。
七、字典(Dictionary)
dict是Python內建的資料結構,在寫Python程式時會經常用到。這裡介紹一下它的get方法和defaultdict方法。
1、get
在獲取dict中的資料時,我們一般使用index的方式,但是如果KEY不存在的時候會丟擲KeyError。這時候你可以使用get方法,使用方法:dict.get(key, default=None),可以避免異常。例如:
d = {'a': 1, 'b': 2} print d.get('c') # None print d.get('c', 14) # 14
2、fromkeys
dict本身有個fromkeys方法,可以通過一個list生成一個dict,不過得提供預設的value,例如:
# ⽤序列做 key,並提供預設value >>> dict.fromkeys(['a', 'b', 'c'], 1) # {'a': 1, 'c': 1, 'b': 1}
3、setdefault
有些情況下,我們需要給dict的KEY一個預設值,你可以這樣寫:
equities = {} for (portfolio, equity) in data: if portfolio in equities: equities[portfolio].append(equity) else: equities[portfolio] = [equity]
上面的實現方式很麻煩,使用dict的setdefault(key, default)方法會更簡潔,更效率。
equities = {} for (portfolio, equity) in data: equities.setdefault(portfolio, []).append(equity)
setdefault方法相當於"get, or set & get",或者相當於"set if necessary, then get"
八、defaultdict
defaultdict是Python2.5之後引入的功能,具體的用法我已經在另外一篇文章中詳細介紹:Python的defaultdict模組和namedtuple模組
九、字典的組裝和拆分(Building & Splitting Dictionaries)
在Python中,你可以使用zip方法將兩個list組裝成一個dict,其中一個list的值作為KEY,另外一個list的值作為VALUE:
>>> given = ['John', 'Eric', 'Terry', 'Michael'] >>> family = ['Cleese', 'Idle', 'Gilliam', 'Palin'] >>> pythons = dict(zip(given, family)) >>> print pythons {'John': 'Cleese', 'Michael': 'Palin', 'Eric': 'Idle', 'Terry': 'Gilliam'}
相反的,你可以使用dict的keys()和values()方法來獲取KEY和VALUE的列表:
>>> pythons.keys() ['John', 'Michael', 'Eric', 'Terry'] >>> pythons.values() ['Cleese', 'Palin', 'Idle', 'Gilliam']
需要注意的是:由於dict本身是無序的,所以通過keys()和values()方法獲得的list的順序已經和原始的list不一樣了。。
十、Python的True值(Truth Values)
在Python中,判斷一個變數是否為True的時候,你可以這樣做:
# 這樣寫 if x: pass # !不要這樣寫 if x == True: pass # 對於list,要這樣寫 if items: pass # !不要這樣寫 if len(items) == 0: pass
Python中的真值物件有以下幾個:
False | True |
---|---|
False (== 0) | True (== 1) |
"" (空字串) | 除 "" 之外的字串(" ", "anything") |
0, 0.0 | 除 0 之外的數字(1, 0.1, -1, 3.14) |
[], (), {}, set() | 非空的list,tuple,set和dict ([0], (None,), ['']) |
None | 大部分的物件,除了明確指定為False的物件 |
對於自己宣告的class,如果你想明確地指定它的例項是True或False,你可以自己實現class的__nonzero__或__len__方法。當你的class是一個container時,你可以實現__len__方法,如下:
class MyContainer(object): def __init__(self, data): self.data = data def __len__(self): """ Return my length. """ return len(self.data)
如果你的class不是container,你可以實現__nonzero__方法,如下:
class MyClass(object): def __init__(self, value): self.value = value def __nonzero__(self): """ Return my truth value (True or False). """ # This could be arbitrarily complex: return bool(self.value)
在Python 3.x中,__nonzero__方法被__bool__方法替代。考慮到相容性,你可以在class定義中加上以下的程式碼:
__bool__ = __nonzero__
十一、enumerate:索引和元素(Index & Item: enumerate)
在Python中,我們在遍歷列表的時候,可以通過enumerate方法來獲取遍歷時的index,比如:
>>> items = 'zero one two three'.split() >>> print list(enumerate(items)) [(0, 'zero'), (1, 'one'), (2, 'two'), (3, 'three')] >>> for (index, item) in enumerate(items): print index, item
enumerate方法是惰性方法,所以它只會在需要的時候生成一項,也因此在上述程式碼print的時候需要包裝一個list。enumerate其實是一個生成器(generator),這個下面會講到。使用enumerate之後,for迴圈變得很簡單:
for (index, item) in enumerate(items): print index, item # compare: index = 0 for item in items: print index, item index += 1 # compare: for i in range(len(items)): print i, items[i]
使用enumerate的程式碼比其他兩個都短,而且更簡單,更容易讀懂。下面的例子可以說明一下enumerate實際返回的資料:一個迭代器,
>>> enumerate(items) <enumerate object at 0x011EA1C0> >>> e = enumerate(items) >>> e.next() (0, 'zero') >>> e.next() (1, 'one') >>> e.next() (2, 'two') >>> e.next() (3, 'three') >>> e.next() Traceback (most recent call last): File "<stdin>", line 1, in ? StopIteration
十二、Python中的變數 & 引用(variables & names)
在很多其他高階語言中,給一個變數賦值時會將"value"放在一個"盒子"裡:
int a = 1;
如圖:
現在,盒子"a"中包含了一個整數 1;將另外一個"value"賦值給同一個變數時,會將"盒子"中的內容替換掉:
a = 2;
如圖:
現在,盒子"a"中包含了一個整數 2;將變數賦值給其他一個變數時,會將"value"拷貝一份放在一個新的"盒子"中:
int b = a;
如圖:
盒子"b"是第二個"盒子",裡面是整數 2的一個拷貝,盒子"a"中是另外一個拷貝。
在Python中,變數沒有資料型別,是附屬於物件的標示符名稱,如下圖:實際,這段表明了像python,PHP這類動態指令碼語言中“變數”包含了兩個內容:1 識別符號名稱 2 識別符號所對應(引用)的值(物件),也就是說“變數”不在是一個容器。
a = 1
這裡,整數 1 物件有一個名字為 "a" 的變數(tag)。如果我們給變數 "a" 重新賦值,對Python來說,只是將變數(tag) "a" 指向另外一個物件:
a = 2
現在,變數 "a" 是附屬在整數物件 2 上面。最初的整數物件 1 已經沒有指向它的變數 "a",它可能還存在,但是我們已經不能通過變數 "a"獲得。當一個物件沒有了指向它的引用的時候,它將會被從記憶體中刪除(垃圾回收)。如果我們將存在的變數賦值給一個新的變數,Python會在已經存在的物件上加上一個指向自己的變數(tag)。
b = a
變數 "a"和"b" 是指向同一個整數物件的。
PS:Python中的變數,引用等設計和其他語言不同,這裡只是將原文翻譯說明了一下,更多的介紹可以參看:Python中的變數、引用、拷貝和作用域
十三、Python方法中引數的預設值(Default Parameter Values)
對於Python初學者來說,Python的方法預設引數有一個很容易犯錯的地方:在預設引數中使用可變物件,甚至有不少Python老鳥也可能會在這個問題上掉坑裡,如果他們不能理解Python的物件引用。。問題如下:
def bad_append(new_item, a_list=[]): a_list.append(new_item) return a_list >>> print bad_append('one') ['one'] >>> print bad_append('two') ['one', 'two']
這個問題的主要原因是:a_list引數的預設值是一個空的list,它在函式定義的時候已經被建立。所以,之後每次呼叫該函式的時候,a_list的預設值都是這個list物件。List,dict和set是可變物件,如果想在函式中獲取一個預設的list(dict or set)物件,正確的做法是在函式中建立:
def good_append(new_item, a_list=None): if a_list is None: a_list = [] a_list.append(new_item) return a_list
十四、字串格式化(String Formatting)
在許多程式語言中都包含有格式化字串的功能,比如C語言中的格式化輸入輸出。Python中內建有對字串進行格式化的操作符 "%" 以及str.format()方法。
1、操作符 "%"
Python中的 "%" 操作符和C語言中的sprintf類似。簡單來說,使用 "%" 來格式化字串的時候,你需要提供一個字串模板和用來插入的值。模板中有格式符,這些格式符為真實值預留位置,並說明真實數值應該呈現的格式。Python用一個tuple將多個值傳遞給模板,每個值對應一個格式符。注意:給定的值一定要和模板中的格式符一一對應!
name = 'xianglong' messages = 3 text = ('Hello %s, you have %i messages' % (name, messages)) print text # Output: Hello xianglong, you have 3 messages
在上面的例子中,"Hello %s, you have %i messages" 是字串模板。%s為第一個格式符,表示一個字串。%i為第二個格式符,表示一個十進位制整數。(name, messages)的兩個元素為替換%s和%i的真實值。在模板和tuple之間,有一個%號分隔,它代表了格式化操作。
常用的格式符如下:
格式 | 描述 |
%% | 百分號 % 標記 |
%s | 字串 (採用str()的顯示) |
%r | 字串 (採用repr()的顯示) |
%c | 字元及其ASCII碼 |
%b | 二進位制整數 |
%d | 十進位制整數 (有符號整數) |
%u | 十進位制整數 (無符號整數) |
%i | 十進位制整數 (有符號整數) |
%o | 八進位制整數 (無符號整數) |
%x | 十六進位制整數 (無符號整數) |
%X | 十六進位制整數 (無符號整數) |
%e | 指數 (基底寫為e) |
%E | 指數 (基底寫為E) |
%f | 浮點數 |
%F | 浮點數,與上相同 |
%g | 指數(e)或浮點數 (根據顯示長度) |
%G | 指數(E)或浮點數 (根據顯示長度) |
%p | 指標(用十六進位制列印值的記憶體地址) |
%n | 儲存輸出字元的數量放進引數列表的下一個變數中 |
使用操作符 "%" 也可以通過字典格式化字串:
values = {'name': name, 'messages': messages} print ('Hello %(name)s, you have %(messages)i messages' % values) # Output: Hello xianglong, you have 3 messages
上面的程式碼中,我們指定了用來格式化的值的名字,然後可以根據name在字典中查詢相應的value。其實,上面的"name"和"messages"已經在local名稱空間中定義,所以,我們可以利用這一點:
print ('Hello %(name)s, you have %(messages)i messages' % locals())
locals()方法返回一個包含所有本地變數的字典。這個功能非常強大,你可以不必擔心提供的values是否和模板匹配;但是同時這個也是非常危險的:你將會暴露整個本地名稱空間給呼叫者,這一點需要你注意。
在Python中,物件有一個__dict__屬性,你可以在格式化字串的時候使用;
print ("We found %(error_count)d errors" % self.__dict__) # 等同於 print ("We found %d errors" % self.error_count)
另外,我們還可以用如下的方式,對字串格式化進一步的控制:%[(name)][flags][width].[precision]typecode,其中:
(name)為命名
flags可以有+,-,' '或0。+表示右對齊。-表示左對齊。' '為一個空格,表示在正數的左側填充一個空格,從而與負數對齊。0表示使用0填充。
width表示顯示寬度
precision表示小數點後精度
比如:
print("%+10x" % 10) # +a print("%04d" % 5) # 0005 print("%6.3f" % 2.3) # 2.300
上面的width, precision為兩個整數。我們可以利用*,來動態代入這兩個量。比如:
print("%.*f" % (4, 1.2)) # 1.2000
Python實際上用4來替換*。所以實際的模板為"%.4f"。
2、str.format()方法
str.format()方法是在Python 2.6中引入的,它通過 {} 和 : 來代替 % ,功能非常強大。具體的用法見下面的例子:
In [1]: name = 'xianglong' In [2]: messages = 4 # 通過位置 In [3]: 'Hello {0}, you have {1} messages'.format(name, messages) Out[3]: 'Hello xianglong, you have 4 messages' # 通過關鍵字引數 In [4]: 'Hello {name}, you have {messages} messages'.format(name=name, messages=messages) Out[4]: 'Hello xianglong, you have 4 messages' # 通過下標 In [5]: 'Hello {0[0]}, you have {0[1]} messages'.format([name, messages]) Out[5]: 'Hello xianglong, you have 4 messages' # 格式限定符:填充與對齊 # ^、<、>分別是居中、左對齊、右對齊,後面頻寬度 # :號後面帶填充的字元,只能是一個字元,不指定的話預設是用空格填充 In [6]: 'Hello {0:>14}, you have {1:>14} messages'.format(name, messages) Out[6]: 'Hello xianglong, you have 4 messages' # 格式限定符:精度與型別f In [7]: '{:.2f}'.format(321.33345) Out[7]: '321.33' # 格式限定符:b、d、o、x分別是二進位制、十進位制、八進位制、十六進位制 In [8]: '{:b}'.format(14) Out[8]: '1110' In [9]: '{:d}'.format(14) Out[9]: '14' In [10]: '{:o}'.format(14) Out[10]: '16' In [11]: '{:x}'.format(14) Out[11]: 'e' # 格式限定符:千位分隔符 In [12]: '{:,}'.format(1234567890) Out[12]: '1,234,567,890'
更多關於Python字串格式化的介紹,可以參看:PEP 3101 -- Advanced String Formatting
十五、迭代器(List comprehensions)
List Comprehensions即迭代器(列表生成式),是Python內建的非常簡單卻強大的可以用來建立list的生成式。在不使用迭代器的時候,建立一個新列表可以使用for和if來實現:
new_list = [] for item in a_list: if condition(item): new_list.append(fn(item))
使用迭代器的話:
new_list = [fn(item) for item in a_list if condition(item)]
列表生成式非常簡潔的,不過是在某種程度上。你可以在列表生成式中使用多個for迴圈和多個if語句,但是兩個以上的for和if語句會讓列表生成式非常複雜,這時候建議直接用for迴圈。根據Zen of Python,選擇更容易讀的方式。下面是一些例子:
>>> [n ** 2 for n in range(10)] [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] >>> [n ** 2 for n in range(10) if n % 2] [1, 9, 25, 49, 81]
十六、生成器(Generator & Generator expressions)
先出一個題:計算1 ~ 100的平方和。最簡單的方法就是使用一個for迴圈:
total = 0 for num in range(1, 101): total += num * num
其實,我們可以使用Python內建的sum方法計算:
# 迭代器(列表生成式) total = sum([num * num for num in range(1, 101)]) # 生成器 total = sum(num * num for num in xrange(1, 101))
生成器和上面提到的迭代器差不多,可以說:生成器是一種特殊的迭代器;但是它們之間有一個很大的區別:迭代器是貪婪的,而生成器是懶惰的,具體來說:迭代器會一次性的計算出整個結果列表,而生成器只在需要的時候計算一個值。這個特性在列表非常大,或者需要一步一步計算的時候非常有用。
在上面的例子中,我們只需要平方和,不需要平方的list,所以我們使用生成器xrange。如果我們計算1 ~ 1000000000的平方和,使用迭代器的話會記憶體溢位,但是生成器卻不會:
total = sum(num * num for num in xrange(1, 1000000000))
在語法上,迭代器會有一個"[]",但是生成器沒有;不過有時候,生成器需要"()",所以,最好每次都帶上。一些自定義的生成器例子:
# 過濾CSV檔案中的空行 def filter_rows(row_iterator): for row in row_iterator: if row: yield row data_file = open(path, 'rb') irows = filter_rows(csv.reader(data_file)) # 檔案讀取:open datafile = open('datafile') for line in datafile: do_something(line)
PS:原文中作者舉了一些工作中的例子,這裡不再贅述,想了解的可以到原文連結中檢視。
十七、排序(Sorting)
在Python中對列表排序非常簡單,比如:
In [1]: a_list = ['Tommy', 'Jack', 'Smith', 'Paul'] In [2]: a_list.sort() In [3]: a_list Out[3]: ['Jack', 'Paul', 'Smith', 'Tommy']
需要注意的是:list的sort()方法會直接在原list變數上排序,改變原本的list物件,並且該方法不會返回一個list物件。如果你需要不改變原list,並且返回新的list物件的話,可以使用Python的orted方法:
In [1]: a_list = ['Tommy', 'Jack', 'Smith', 'Paul'] In [2]: b_list = sorted(a_list) In [3]: b_list Out[3]: ['Jack', 'Paul', 'Smith', 'Tommy'] In [4]: a_list Out[4]: ['Tom', 'Jack', 'Smith', 'Paul']
但是,如果你想對一個list進行排序,不過不想使用預設的排序方式,比如你可能需要先根據第二行排序,再根據第四行排序。這時候,我們也可以使用sort()方法,但是得提供一個自定義的排序方法:
In [1]: def custom_cmp(item1, item2): ...: return cmp((item1[1], item1[3]), (item2[1], item2[3])) ...: In [2]: a_list = ['Tommy', 'Jack', 'Smith', 'Paul'] In [3]: a_list.sort(custom_cmp) In [4]: a_list Out[4]: ['Jack', 'Paul', 'Smith', 'Tommy']
這種方法可以實現,但是在list比較大的情況下效率很低。下面介紹兩種其他的方法。
1、DSU排序方法
DSU即Decorate-Sort-Undecorate,中文就是"封裝-排序-解封"。DSU方法不會建立自定義的排序方法,而是建立一個輔助的排序列表,然後對這個列表進行預設排序。需要說明的是:DSU方法是一種比較老的方法,現在已經基本上不使用了,不過這裡還是給出一個簡單的例子說明一下:
# Decorate: to_sort = [(item[1], item[3], item) for item in a_list] # Sort: to_sort.sort() # Undecorate: a_list = [item[-1] for item in to_sort]
上述程式碼第一行建立了一個tuple的list,tuple中的前兩項是用來排序的欄位,最後一項是原資料;第二行使用sort()方法對輔助的list進行預設的排序;最後一行是從已經排序的輔助list中獲取原資料,重新組成list。
這種方法是使用複雜度和記憶體空間來減少計算時間,比較簡單,也比較快,但是我們得複製原列表的資料。
2、KEY方法
自從Python 2.4之後,list.sort()和sorted()都新增了一個key引數用來指定一個函式,這個函式作用於每個list元素,在做cmp之前呼叫。key引數是一個函式,這個函式有一個引數,返回一個用來排序的關鍵字。這種排序方法很快,因為key方法在每個輸入的record上只執行一次。你可以使用Python內建的函式(len, str.lower)或者自定義函式作為key引數,下面是一個簡單的例子:
In [1]: a_list = ['Tommy', 'Jack', 'Smith', 'Paul'] In [2]: def my_key(item): ...: return (item[1], item[3]) ...: In [3]: a_list.sort(key=my_key) In [4]: a_list Out[4]: ['Jack', 'Paul', 'Smith', 'Tommy']
十八、EAFP vs LBYL
檢查資料可以讓程式更健壯,用術語來說就是防禦性程式設計。檢查資料的時候,有EAFP和LBYL兩種不同的程式設計風格,具體的意思如下:
LBYL: Look Before You Leap,即事先檢查;
EAFP: It's Easier to Ask Forgiveness than Permission,即不檢查,出了問題由異常處理來處理。
異常處理總是比事先檢查容易,因為你很難提前想到所有可能的問題。所以,一般情況下編碼時會傾向使用EAFP風格,但它也不是適應所有的情況。兩個風格的優缺點如下:
d = {} words = ['a', 'd', 'a', 'c', 'b', 'z', 'd'] # LBYL for w in words: if w not in d: d[w] = 0 d[w] += 1 # EAFP for w in words: try: d[w] += 1 except KeyError: d[w] = 1
對於LBYL,容易打亂思維,本來業務邏輯用一行程式碼就可以搞定的。卻多出來了很多行用於檢查的程式碼。防禦性的程式碼跟業務邏輯混在一塊降低了可讀性。而EAFP,業務邏輯程式碼跟防禦程式碼隔離的比較清晰,更容易讓開發者專注於業務邏輯。不過,異常處理會影響一點效能。因為在發生異常的時候,需要進行保留現場、回溯traceback等操作。但其實效能相差不大,尤其是異常發生的頻率比較低的時候。
另外,需要注意的是,如果涉及到原子操作,強烈推薦用EAFP風格。比如我某段程式邏輯是根據redis的key是否存在進行操作。如果先if exists(key),然後do something。這樣就變成2步操作,在多執行緒併發的時候,可能key的狀態已經被其他執行緒改變了。而用EAFP風格則可以確保原子性。
PS:在使用EAFP風格捕獲異常時,儘量指明具體的異常,不要直接捕獲Exception。否則會捕獲到其他未知的異常,如果有問題,你會很難去定位(debug)。
十九、引用(Importing)
Python中的引用:
from module import *
你可能在其他地方見過這種使用萬用字元*的引用方式,可能你也比較喜歡這種方式。但是,這裡要說的是:請不要使用這種引用方式!
萬用字元引用的方式屬於Python中的陰暗面,這種方式會導致名稱空間汙染的問題。你可能會在本地名稱空間中得到意想不到的東西,而且這種方式引入的變數可能將你在本地定義的變數覆蓋,在這種情況下,你很難弄清楚變數來自哪裡。所以,萬用字元引用的方式雖然方便,但可能會導致各種各樣奇怪的問題,在Python專案中儘量不要用這種方式。
在Python中,大家比較認同的import方式有以下幾個規則:
1、通過模組引用變數(Reference names through their module)
這種方式直接import的是模組,然後通過模組訪問其中的變數,Class和方法。使用這種方式可以很清晰的知道變數來自哪裡:
import module module.name
2、模組名比較長時使用短名字(alias)
import long_module_name as mod mod.name
3、直接引用你需要的變數名
from module import name name
二十、模組與指令碼(Modules & Scripts)
為了使一個Python檔案既可以被引用,又可以直接執行,你可以在Python檔案中加上這樣的程式碼:
if __name__ == '__main__': # script code here
當被引用時,一個模組(module)的__name__屬性會被設定為該檔案的檔名(不包括.py字尾)。所以,上面程式碼片段中if語句中的指令碼在被引用的時候不會執行。當把Python檔案作為一個指令碼執行的時候,__name__屬性則被設定為"__main__",這時候if語句中的指令碼才會被執行。
最好不要在Python檔案中直接寫可執行的語句,應該將這些程式碼放在方法或類裡面,必要的時候放在"if __name__ == '__main__':"中。一個Python檔案的結構可以參考下面的:
#!/usr/bin/env python # -*- coding: utf-8 -*- """ 文件 module docstring """ # 引用 imports # 常量 constants # 異常 exception classes # 方法 interface functions # 類 classes # 內部方法和類 internal functions & classes def main(...): ... if __name__ == '__main__': status = main() sys.exit(status)
二十一、命令列解析(Commend-Line Processing)
Python是一種指令碼語言,有時候我們會直接在命令列執行Python檔案,這時候可能需要解析命令列傳入的引數,下面是一個例子:cmdline.py
#!/usr/bin/env python """ Module docstring. """ import sys import optparse def process_command_line(argv): """ Return a 2-tuple: (settings object, args list). `argv` is a list of arguments, or `None` for ``sys.argv[1:]``. """ if argv is None: argv = sys.argv[1:] # initialize the parser object: parser = optparse.OptionParser( formatter=optparse.TitledHelpFormatter(width=78), add_help_option=None) # define options here: parser.add_option( # customized description; put --help last '-h', '--help', action='help', help='Show this help message and exit.') settings, args = parser.parse_args(argv) # check number of arguments, verify values, etc.: if args: parser.error('program takes no command-line arguments; ' '"%s" ignored.' % (args,)) # further process settings & args if necessary return settings, args def main(argv=None): settings, args = process_command_line(argv) # application code here, like: # run(settings, args) return 0 # success if __name__ == '__main__': status = main() sys.exit(status)
二十二、包(Packages)
Python中包的設計與引用規則,包的設計例子:
package/ __init__.py module1.py subpackage/ __init__.py module2.py
建議使用上面的方式來組織你的專案,儘量減小引用路徑,明確引用物件,避免引用衝突。引用示例:
import package.module1 from package.subpackage import module2 from package.subpackage.module2 import name
我們可以通過__future__模組使用Python 3.0的功能:absolute_import。方法如下:
from __future__ import absolute_import
簡單介紹一下相對引入和絕對引入的概念:
相對匯入:在不指明 package 名的情況下匯入自己這個 package 的模組,比如一個 package 下有 a.py 和 b.py 兩個檔案,在 a.py 裡 from . import b 即是相對匯入 b.py。
絕對匯入:指明頂層 package 名。比如 import a,Python 會在 sys.path 裡尋找所有名為 a 的頂層模組。
引入absolute_import之後不是支援了絕對引入,而是拒絕相對引入。
簡單比複雜好
除錯程式的難度是寫程式碼的兩倍。因此,只要你的程式碼寫的儘可能的清楚,那麼你在除錯程式碼時就不需要那麼地有技巧。(Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it. ) -- Brian Kernighan。所以,儘量保持你的程式足夠簡單。
不要重複造輪子
在你寫程式碼之前,你需要先看一下有沒有其他人已經實現了類似的功能。你可以從下面的幾個地方尋找:
1、Python標準庫
2、Python第三方LIB, PYPI(Python Package Index),地址:PYPI
3、搜尋引擎,Google,百度等。。
參考
Code Like a Pythonista: Idiomatic Python
Over!
via:xianglong.me
相關文章
- 像bootstrap一樣的去做web程式設計bootWeb程式設計
- 像建築設計師一樣去寫程式
- 如何成為一個程式設計師高手程式設計師
- 像鳥一樣思考更好的並行程式設計並行行程程式設計
- 像程式設計師那樣去求婚程式設計師
- 故事樣設計——怎樣像使用者一樣思考?
- 如何讓 Python 像 Julia 一樣快地執行Python
- 如何像程式設計師一樣思考 - 解決問題的經驗與教訓程式設計師
- 8月書訊 | 像大師級程式設計師一樣思考程式設計師
- 像程式設計師一樣思考——提高解決問題的能力程式設計師
- 程式設計師如何成為程式設計高手,並以此創業程式設計師創業
- 趣文:如果像招聘程式設計師那樣租車程式設計師
- 像招程式設計師那樣招司機,結果……程式設計師
- 那些程式設計高手是如何練成的?程式設計
- iOS程式設計師如何成為程式設計高手,並以此創業iOS程式設計師創業
- 像CFO一樣思考(3)- 電商閉環設計思考
- 趣文:如果像招聘程式設計師那樣招聘木匠程式設計師
- 像老大一樣優化Python優化Python
- 兩種程式設計高手程式設計
- 程式設計15年,如何才能成不了高手?程式設計
- 不久我們將不用計算機程式設計,只需像狗一樣訓練它們計算機程式設計
- 面對一個Bug,高手程式設計師是如何解決問題的?程式設計師
- 像談戀愛那樣去招頂級程式設計師吧程式設計師
- 如何像資料分析師一樣思考?
- 微信小程式如何像webview一樣載入html5網頁微信小程式WebViewHTML網頁
- 心目中的程式設計高手程式設計
- pythonista 遊戲程式碼更新Python遊戲
- 像使用水電一樣使用雲端計算
- 怎樣尊重一個程式設計師程式設計師
- 心目中的程式設計高手(zt)程式設計
- 遠見卓識 像CEO一樣編寫程式碼!
- 利用 Numba 加速你的 Python 程式碼,使其變得像 C++ 一樣快PythonC++
- 學Python的程式設計師,程式設計能力都“退化”成什麼樣了?Python程式設計師
- 如何在Windows下像Mac一樣優雅開發WindowsMac
- 如何成為Python高手Python
- 怎麼樣學好python技術當一名程式設計師Python程式設計師
- MagicArray:像php一樣,讓Go業務程式碼不再卷!PHPGo
- [譯] 斷點:像專家一樣除錯程式碼斷點除錯