最近在網上看到一篇介紹Pythonic編程的文章:Code Like a Pythonista: Idiomatic Python,其實做者在2006的PyCon會議後就寫了這篇文章,寫這篇文章的主要緣由是做者發現不少有經驗的Pythoner寫出的代碼不夠Pythonic。我以爲這篇文章很不錯,因此將它用中文寫了下來(不是逐字的翻譯,中間加了一些本身的理解),分享給你們。另:因爲本人平時時間有限,這篇文章翻譯了比較長的時間,若是你發現了什麼不對的地方,歡迎指出。。php
The Zen of Python是Python語言的指導原則,遵循這些基本原則,你就能夠像個Pythonista同樣編程。具體內容你能夠在Python命令行輸入import this看到:html
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 20python
Abelson & Sussman在《計算機程序的構造和解釋》一書中說道:程序是寫來給人讀的,只是順帶讓機器執行。因此,咱們在編碼時應該儘可能讓它更易讀懂。PEP8是Python的編碼規範,官方文檔見:PEP 8,PEP是Python Enhancement Proposal的縮寫。PEP8包括不少編碼的規範,下面主要介紹一下縮進和命名等內容。git
空格和縮進(WhiteSpace and Indentation)github
空格和縮進在Python語言中很是重要,它替代了其餘語言中{}的做用,用來區分代碼塊和做用域。在這方面PEP8有如下的建議:redis
一、每次縮進使用4個空格 二、不要使用Tab,更不要Tab和空格混用 三、兩個方法之間使用一個空行,兩個Class之間使用兩個空行 四、添加一個空格在字典、列表、序列、參數列表中的「,「後,以及在字典中的」:「以後,而不是以前 五、在賦值和比較兩邊放置一個空格(參數列表中除外) 六、緊隨括號後面或者參數列表前一個字符不要存在空格
Python命名express
命名規範是編程語言的基礎,並且大部分的規範對於高級語言來講都是同樣的,Python的基本規範以下:編程
一、方法 & 屬性:joined_lower 二、常量:joined_lower or ALL_CAPS 三、類:StudlyCaps 四、類屬性:interface, _internal, __private 五、camelCase only to conform to pre-existing conventions
以上內容只是對PEP8作了很是簡單的介紹,因爲今天的主題不在於此,因此就不在這裏多講。想要更加深刻的瞭解Python編碼規範,能夠閱讀PEP8官方文檔和Google Python編碼規範等內容。數據結構
在其餘語言中,交換兩個變量值的時候,能夠這樣寫:多線程
temp = a a = b b = temp
在Python中,咱們能夠簡單的這樣寫:
b, a = a, b
可能你已經在其餘地方見過這種寫法,可是你知道Python是如何實現這種語法的嗎?首先,逗號(,)是Python中tuple數據結構的語法;上面的語法會執行一下的操做:
一、Python會先將右邊的a, b生成一個tuple(元組),存放在內存中;
二、以後會執行賦值操做,這時候會將tuple拆開;
三、而後將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中比較有用的一個功能,不過有不少人不知道(我也是接觸Python好久以後才知道的)。。在Python的交互式控制檯中,當你計算一個表達式或者調用一個方法的時候,運算的結果都會放在一個臨時的變量 _ 裏面。_(下劃線)用來存儲上一次的打印結果,好比:
>>> import math >>> math.pi / 3 1.0471975511965976 >>> angle = _ >>> math.cos(angle) 0.50000000000000011 >>> _ 0.50000000000000011
PS:當返回結果爲None的時候,控制檯不會打印,_ 裏面存儲的值也就不會改變。
假如如今有一個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時將拋出異常。
當你須要判斷一個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的時候執行效率會比較快,代碼也更簡潔。
dict是Python內置的數據結構,在寫Python程序時會常常用到。這裏介紹一下它的get方法和defaultdict方法。
一、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
二、fromkeys
dict自己有個fromkeys方法,能夠經過一個list生成一個dict,不過得提供默認的value,例如:
# ⽤序列作 key,並提供默認value >>> dict.fromkeys(['a', 'b', 'c'], 1) # {'a': 1, 'c': 1, 'b': 1}
三、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是Python2.5以後引入的功能,具體的用法我已經在另一篇文章中詳細介紹:Python的defaultdict模塊和namedtuple模塊
在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的時候,你能夠這樣作:
# 這樣寫 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__
在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
在不少其餘高級語言中,給一個變量賦值時會將"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初學者來講,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
在許多編程語言中都包含有格式化字符串的功能,好比C語言中的格式化輸入輸出。Python中內置有對字符串進行格式化的操做符 "%" 以及str.format()方法。
一、操做符 "%"
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"。
二、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即迭代器(列表生成式),是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]
先出一個題:計算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:原文中做者舉了一些工做中的例子,這裏再也不贅述,想了解的能夠到原文連接中查看。
在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比較大的狀況下效率很低。下面介紹兩種其餘的方法。
一、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。
這種方法是使用複雜度和內存空間來減小計算時間,比較簡單,也比較快,可是咱們得複製原列表的數據。
二、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和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)。
Python中的引用:
from module import *
你可能在其餘地方見過這種使用通配符*的引用方式,可能你也比較喜歡這種方式。可是,這裏要說的是:請不要使用這種引用方式!
通配符引用的方式屬於Python中的陰暗面,這種方式會致使命名空間污染的問題。你可能會在本地命名空間中獲得意想不到的東西,並且這種方式引入的變量可能將你在本地定義的變量覆蓋,在這種狀況下,你很難弄清楚變量來自哪裏。因此,通配符引用的方式雖然方便,但可能會致使各類各樣奇怪的問題,在Python項目中儘可能不要用這種方式。
在Python中,你們比較認同的import方式有如下幾個規則:
一、經過模塊引用變量(Reference names through their module)
這種方式直接import的是模塊,而後經過模塊訪問其中的變量,Class和方法。使用這種方式能夠很清晰的知道變量來自哪裏:
import module module.name
二、模塊名比較長時使用短名字(alias)
import long_module_name as mod mod.name
三、直接引用你須要的變量名
from module import name name
爲了使一個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)
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)
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。因此,儘可能保持你的程序足夠簡單。
不要重複造輪子
在你寫代碼以前,你須要先看一下有沒有其餘人已經實現了相似的功能。你能夠從下面的幾個地方尋找:
一、Python標準庫
二、Python第三方LIB, PYPI(Python Package Index),地址:PYPI
三、搜索引擎,Google,百度等。。
參考
Code Like a Pythonista: Idiomatic Python
LBYL與EAFP兩種防護性編程風格
Over!