改善 Python 程式的 91 個建議(二)
建議 24:遵循異常處理的幾點基本原則
異常處理的幾點原則:
- 注意異常的粒度,不推薦在 try 中放入過多的程式碼
- 謹慎使用單獨的 except 語句處理所有異常,最好能定位具體的異常
- 注意異常捕獲的順序,在適合的層次處理異常,Python 是按內建異常類的繼承結構處理異常的,所以推薦的做法是將繼承結構中子類異常在前丟擲,父類異常在後丟擲
- 使用更為友好的異常資訊,遵守異常引數的規範
建議 25:避免 finally 中可能發生的陷阱
當 finally 執行完畢時,之前臨時儲存的異常將會再次被丟擲,但如果 finally 語句中產生了新的異常或執行了 return 或 break 語句,那麼臨時儲存的異常將會被丟失,從而異常被遮蔽。
在實際開發中不推薦 finally 中使用 return 語句進行返回。
建議 26:深入理解 None,正確判斷物件是否為空
型別FalseTrue布林False (與0等價)True (與1等價)字串”"( 空字串)非空字串,例如 ” “, “blog”數值0, 0.0非0的數值,例如:1, 0.1, -1, 2容器[], (), {}, set()至少有一個元素的容器物件,例如:[0], (None,), ['']NoneNone非None物件
>>> id(None) 10743840 >>> a = None >>> id(a) 10743840 >>> l = [] >>> if l is not None: # 判斷邏輯 l 不為空 ... print('l is {}'.format(l)) ... else: ... print('l is empty') ... l is [] >>> if l: # #3 正確的判斷形式 ... print('Do something...') ... else: ... print('Do other thing...') ... Do other thing...
#3執行中會呼叫__nonzero__()來判斷自身物件是否為空並返回0/1或True/False,如果沒有定義該方法,Python 將呼叫__len__()進行判斷,返回 0 表示為空。如果一個類既沒有定義__len__()又沒有定義__nonzero__(),該類例項用 if 判斷為True。
建議 27:連線字串優先使用 join 而不是 +
這一點之前我在博文裡總結過,+涉及到更多的記憶體操作。
建議 28:格式化字串時儘量使用 .format 而不是 %
同上。
建議 29:區別對待可變物件和不可變物件
Python 中一切皆物件,每個物件都有一個唯一的識別符號(id)、型別(type)和值。數字、字串、元組屬於不可變物件,字典、列表、位元組陣列屬於可變物件。
class Student(object): def __init__(self, name, course=[]): # 問題就出在這裡 self.name = name self.course = course def addcourse(self, coursename): self.course.append(coursename) def printcourse(self): for item in self.course: print(item) stuA = Student('Wang yi') stuA.addcourse('English') stuA.addcourse('Math') print("{}'s course: ".format(stuA.name)) stuA.printcourse() print('---------------------------') stuB = Student('Su san') stuB.addcourse('Chinese') stuB.addcourse('Physics') print("{}'s course: ".format(stuB.name)) stuB.printcourse() # run Wang yi's course: English Math --------------------------- Su san's course: English Math Chinese Physics
預設引數在初始化時僅僅被評估一次,以後直接使用第一次評估的結果,course 指向的是 list 的地址,每次操作的實際上是 list 所指向的具體列表,所以對於可變物件的更改會直接影響原物件。
最好的方法是傳入None作為預設引數,在建立物件的時候動態生成列表。
>>> list1 = ['a', 'b', 'c'] >>> list2 = list1 >>> list1.append('d') >>> list2 ['a', 'b', 'c', 'd'] >>> list3 = list1[:] # 可變物件的切片操作相當於淺拷貝 >>> list3.remove('a') >>> list3 ['b', 'c', 'd'] >>> list1 ['a', 'b', 'c', 'd']
建議 30:[]、() 和 {} 一致的容器初始化形式
>>> list1 = [['Hello', 'World'], ['Goodbye', 'World']] >>> list2 = [[s.upper() for s in xs] for xs in list1] >>> list2 [['HELLO', 'WORLD'], ['GOODBYE', 'WORLD']] >>> [v**2 if v%2 == 0 else v+1 for v in [2, 3, 4, -1] if v>0] [4, 4, 16]
其實就是列表生成式、元組生成式和字典生成式。
建議 31:記住函式傳參既不是傳值也不是傳引用
正確的說法是傳物件(call by object)或傳物件的引用(call-by-object-reference),函式引數在傳遞過程中將整個物件傳入,對可變物件的修改在函式外部以及內部都可見,對不可變物件的”修改“往往是通過生成一個新物件然是賦值實現的。
建議 32:警惕預設引數潛在的問題
其中就是預設引數如果是可變物件,在呼叫者和被呼叫者之間是共享的。
import time # 對當前系統時間進行處理 def report(when=time.time): # 而不是when=time.time() pass
建議 33:慎用變長引數
原因如下:
- 使用過於靈活,導致函式簽名不夠清晰,存在多種呼叫方式
- 使用*args和**kw簡化函式定義就意味著函式可以有更好的實現方法
使用場景:
- 為函式新增一個裝飾器
- 引數數目不確定
- 實現函式的多型或子類需要呼叫父類的某些方法時
建議 34:深入理解 str() 和repr() 的區別
總結幾點:
- str()面向使用者,返回使用者友好和可讀性強的字串型別;repr()面向 Python 直譯器或開發人員,返回 Python 直譯器內部的含義
- 直譯器中輸入a預設呼叫repr(),而print(a)預設呼叫str()
- repr()返回值一般可以用eval()還原物件:obj == eval(repr(obj))
- 以上兩個方法分別呼叫內建的__str__()和__repr__(),一般來說類中都應該定義__repr__(),但當可讀性比準確性更為重要時應該考慮__str__(),使用者實現__repr__()方法的時候最好保證其返回值可以用eval()是物件還原
建議 35:分清 staticmethod 和 classmethod 的適用場景
這兩種方法之前已經總結過了的,下面我們只討論它們的使用場景。
呼叫類方法裝飾器的修飾器的方法,會隱式地傳入該物件所對應的類,可以動態生成對應的類的類變數,同時如果我們期望根據不同的型別返回對應的類的例項,類方法才是正確的解決方案。
反觀靜態方法,當我們所定義的方法既不跟特定的例項相關也不跟特定的類相關,可以將其定義為靜態方法,這樣使我們的程式碼能夠有效地組織起來,提高可維護性。
當然,也可以考慮定義一個模組,將一組的方法放入其中,通過模組來訪問。
第 4 章 庫
建議 36:掌握字串的基本用法
# 小技巧:Python 遇到未閉合的小括號會自動將多行程式碼拼接為一行 >>> s = ('SELECT * ' ... 'FROM table ' ... 'WHERE field="value"') >>> s 'SELECT * FROM table WHERE field="value"' # Python2 中使用 basestring 正確判斷一個變數是否是字串 # 性質判斷 isalnum() isalpha() isdigit() islower() isupper() isspace() istitle() # 查詢替換 startswith(prefix[, start[, end]]) endswith(suffix[, start[, end]]) # prefix引數可以接收 tuple 型別的實參 count(sub[, start[, end]]) find(sub[, start[, end]]) index(sub[, start[, end]]) rfind(sub[, start[, end]]) rindex(sub[, start[, end]]) replace(old, new[, count]) # count是指的替換次數,不指定就全部替換 # 切分 partition(sep) rpartition(sep) splitlines([keepends]) split([sep, [, maxsplit]]) rsplit([sep[, maxsplit]]) # partition 返回一個3個元素的元組物件 # 變形 lower() upper() capitalize() swapcase() title() # 刪減填充 strip([chars]) lstrip([chars]) rstrip([chars]) # 沒有提供chars預設是空白符,由string.whitespace 常量定義 center(width[, fillchar]) ljuct(width[, fillchar]) rjust(width[, fillchar]) zfill(width) expandtabs([tabszie])
下面來介紹一些易混淆的地方:
>>> ' hello world'.split() ['hello', 'world'] >>> ' hello world'.split(' ') ['', '', 'hello', 'world'] >>> 'hello wORld'.title() 'Hello World' >>> import string >>> string.capwords(' hello world!') 'Hello World!' >>> string.whitespace ' \t\n\r\x0b\x0c'
建議 37:按需選擇 sort() 或者 sorted()
# 函式原型 sorted(iterable[, cmp[, key[, reverse]]]) # 返回一個排序後的列表 s.sort([cmp[, key[, reverse]]]) # 直接修改原列表,返回為None >>> persons = [{'name': 'Jon', 'age': 32}, {'name': 'Alan', 'age': 50}, {'name': 'Bob', 'age': 23}] >>> sorted(persons, key=lambda x: (x['name'], -x['age'])) [{'name': 'Alan', 'age': 50}, {'name': 'Bob', 'age': 23}, {'name': 'Jon', 'age': 32}] >>> a = (1, 2, 4, 2, 3) >>> sorted(a) [1, 2, 2, 3, 4]
所以如果實際過程中需要保留原有列表,可以使用sorted()。sort()不需要複製原有列表,消耗記憶體較小,效率較高。同時傳入引數key比傳入引數cmp效率要高,cmp傳入的函式在整個排序過程中會呼叫多次,而key針對每個元素僅作一次處理。
建議 38:使用 copy 模組深拷貝物件
- 淺拷貝(shallow copy):構造一個新的複合物件並將從原物件中發現的引用插入該物件中。工廠函式、切片操作、copy 模組中的 copy 操作都是淺拷貝
- 深拷貝(deep copy):針對引用所指向的物件繼續執行拷貝,因此產生的物件不受其它引用物件操作的影響。深拷貝需要依賴 copy 模組的 deepcopy() 操作
在 python 中,標識一個物件唯一身份的是:物件的id(記憶體地址),物件型別,物件值,而淺拷貝就是建立一個具有相同型別,相同值但不同id的新物件。因此使用淺拷貝的典型使用場景是:物件自身發生改變的同時需要保持物件中的值完全相同,比如 list 排序:
def sorted_list(olist, key=None): copied_list = copy.copy(olist) copied_list.sort(key=key) return copied_list a = [3, 2, 1] # [3, 2, 1] b = sorted_list(a) # [1, 2, 3]
深拷貝不僅僅拷貝了原始物件自身,也對其包含的值進行拷貝,它會遞迴的查詢物件中包含的其他物件的引用,來完成更深層次拷貝。因此,深拷貝產生的副本可以隨意修改而不需要擔心會引起原始值的改變:
>>> a = [1, 2] >>> b = [a, a] >>> b [[1, 2], [1, 2]] >>> from copy import deepcopy >>> c = deepcopy(b) >>> id(b[0]) == id(c[0]) False >>> id(b[0]) == id(b[1]) True >>> c [[1, 2], [1, 2]] >>> c[0].append(3) >>> c [[1, 2, 3], [1, 2, 3]]
使用 _copy_ 和 __deepcopy__ 可以完成對一個物件拷貝的定製。
建議 39: 使用 Counter 進行計數統計
常見的計數統計可以使用dict、defaultdict、set和list,不過 Python 提供了一個更優雅的方式:
>>> from collections import Counter >>> some_data = {'a', '2', 2, 3, 5, 'c', '7', 4, 5, 'd', 'b'} >>> Counter(some_data) Counter({'7',: 1, 2: 1, 3: 1, 4: 1, 5: 1, '2': 1, 'b': 1, 'a': 1, 'd': 1, 'c': 1})
Counter 類屬於字典類的子類,是一個容器物件,用來統計雜湊物件,支援+、-、&、|,其中&和|分別返回兩個 Counter 物件各元素的最小值和最大值。
# 初始化 Counter('success') Counter(s=3, c=2, e=1, u=1) Counter({'s': 3, 'c': 2, 'u': 1, 'e': 1}) # 常用方法 list(Counter(some_data).elements()) # 獲取 key 值 Counter(some_data).most_common(2) # 前 N 個出現頻率最高的元素以及對應的次數 (Counter(some_data))['y'] # 訪問不存在的元素返回 0 c = Counter('success') c.update('successfully') # 更新統計值 c.subtract('successfully') # 統計數相減,允許為0或為負
建議 40:深入掌握 ConfigParser
幾乎所有的應用程式都會讀取配置檔案,ini是一種比較常見的檔案格式:
[section1] option1=0
Python 提供標準庫 ConfigParser 來支援它:
import ConfigParser conf = ConfigParser.ConfigParser() conf.read('example.conf') print(conf.get('section1', 'in_default'))
再來看個SQLAlchemy配置檔案的例子:
[DEFAULT] conn_str = %(dbn)s://%(user)s:%(pw)s@%(host)s:%(port)s/%(db)s dbn = mysql user = root host = localhost port = 3306 [db1] user = aaa pw = ppp db = example [db2] host = 192.168.0.110 pw = www db = example
import ConfigParser conf = ConfigParser.ConfigParser() conf.read('format.conf') print(conf.get('db1', 'conn_str')) print(conf.get('db2', 'conn_str'))
相關文章
- 改善 Python 程式的 91 個建議Python
- 改善 Python 程式的 91 個建議(一)Python
- 改善 Python 程式的 91 個建議(三)Python
- 改善 Python 程式的 91 個建議(四)Python
- 改善 Python 程式的 91 個建議(六)Python
- 《改善python程式的91個建議》讀書筆記Python筆記
- 編寫高質量程式碼 改善Python程式的91個建議Python
- 讀改善c#程式碼157個建議:建議1~3C#
- 讀改善c#程式碼157個建議:建議4~6C#
- 讀改善c#程式碼157個建議:建議7~9C#
- 讀改善c#程式碼157個建議:建議10~12C#
- 讀改善c#程式碼157個建議:建議13~15C#
- Flutter 6 個建議改善你的程式碼結構Flutter
- 編寫高質量程式碼:改善Java程式的151個建議(第4章:字串___建議52~55)Java字串
- 編寫高質量程式碼:改善Java程式的151個建議(第4章:字串___建議56~59)Java字串
- 《編寫高質量程式碼:改善Java程式的151個建議》筆記Java筆記
- 程式設計師必備基礎:改善Java程式的20個實用建議程式設計師Java
- 編寫高質量程式碼:改善Java程式的151個建議(第2章:基本型別___建議21~25)Java型別
- 編寫高質量程式碼:改善Java程式的151個建議(第2章:基本型別___建議26~30)Java型別
- 改善網頁設計的10個絕佳SEO建議網頁
- 編寫高質量程式碼:改善Java程式的151個建議(第7章:泛型和反射___建議93~97)Java泛型反射
- 編寫高質量程式碼:改善Java程式的151個建議(第5章:陣列和集合___建議60~64)Java陣列
- 編寫高質量程式碼:改善Java程式的151個建議(第5章:陣列和集合___建議65~69)Java陣列
- 編寫高質量程式碼:改善Java程式的151個建議(第5章:陣列和集合___建議70~74)Java陣列
- 編寫高質量程式碼:改善Java程式的151個建議(第5章:陣列和集合___建議75~78)Java陣列
- 編寫高質量程式碼:改善Java程式的151個建議(第5章:陣列和集合___建議79~82)Java陣列
- 編寫高質量程式碼:改善Java程式的151個建議(第3章:類、物件及方法___建議41~46)Java物件
- 編寫高質量程式碼:改善Java程式的151個建議(第3章:類、物件及方法___建議47~51)Java物件
- 編寫高質量程式碼:改善Java程式的151個建議(第3章:類、物件及方法___建議31~35)Java物件
- 編寫高質量程式碼:改善Java程式的151個建議(第3章:類、物件及方法___建議36~40)Java物件
- 改善 ASP.NET MVC 程式碼庫的 5 點建議ASP.NETMVC
- 改善Java文件的理由、建議和技巧Java
- 編寫高質量程式碼:改善Java程式的151個建議(第6章:列舉和註解___建議83~87)Java
- 編寫高質量程式碼:改善Java程式的151個建議(第6章:列舉和註解___建議88~92)Java
- 編寫高質量程式碼:改善Java程式的151個建議(第8章:異常___建議110~113)Java
- 編寫高質量程式碼:改善Java程式的151個建議(第8章:異常___建議114~117)Java
- 編寫高質量程式碼:改善Java程式的151個建議(第7章:泛型和反射___建議98~101)Java泛型反射
- 編寫高質量程式碼:改善Java程式的151個建議(第7章:泛型和反射___建議102~105)Java泛型反射