用pythonic方式思考
- 確認使用的python的版本, 優先使用python3
- 遵守PEP8風格, 保護屬性_leading_underscore, 私有屬性__double_leading_underscore, 類與異常CapitalWord, 模塊級別的變量ALL_CAPS。 不要通過len(somelist)來判斷somelist是否非空, 而是if (not) somelist.
- python3: bytes(原始8bit值), str(Unicode); python2: str(原始8bit值), unicode(Unicode). 通過encode/decode('utf-8')可以來統一編碼, 以“中文” 為例, encode就是系統碼(b'\xe4\xb8\xad\xe6\x96\x87'), decode就是用戶可識別碼(你好). 另外, 在python中, 讀寫文件統一采用模式('wb', 'rb')。
- 輔助函數代替復雜的表達式,簡潔
- 切片操作
- [start:end:stride]是另外一種靈活的處理list的方式, 但是若正負值混用的話可能造成困擾, itertools中的islide不允許start, end, stride為負值, 若有需求可以采用
- 用list comprehension代替map, filter等操作, 如[x**2 for x in list]
- 不要使用兩個以上的list comprehension
- 用生成器, 如果數據大的話。 其實就是[]改成()這樣就變成generator了,
- 用enumerate代替range, 使得代碼更加簡潔, 其中, 可以指定enumerate開始的index, 如enumerate(list, 1), 則index是從1開始計數的
- 用zip同時遍歷兩個迭代器, 但是zip(a, b) 若其中一個已經耗盡的話, 就停止了, 可以改成itertools中的zip_longest來代替(izip_longest in python2)
- 不要再for/while后面寫else
- 合理使用try/except/else/finally. try: finally: 可以用finally來關閉文件, 因為它一定會被執行。 try: except: else: 用except來檢測異常, 若通過了try, else塊也會被執行, 這樣可以減少try語塊那里的代碼量。 終極用法就是try: except: else: finally.
函數
- 盡量返回Exception而不是None, 因為在python的if判斷中None, 0, '', [] 可能都是等價的。
- 如何在閉包(closure)中使用外圍作用域的變量: nonlocal 盡量避免這種用法
- 使用generator代替result list, 代碼更加簡潔, 使用更少的內存
- generator是有狀態的, 調用一輪之后就會遇到StopIterationException, 一種方法是不用iter, 而是iter(), 如用sum(iter())代替sum(iter), 這樣就是產生新的generator而不是原來的generator了。另外一種是建立一個類, 定義iter方法, 這也是可迭代的。
- 使用*args接受變長的輸入
- 使用關鍵字參數(如a=9)來表達可選的行為, 并且記住不要以位置參數的行為給它賦值, 如函數foo(a, b=9), 若調用的時候b要重新賦值,不要用foo(1, 2) 這種形式, 而應該是foo(1, b=2)。
- 使用None表示具有動態默認值的參數, 并且用docstring描述其行為。 如def log(message, when=datatime.now())的話when只在程序運行的時候會被加載一次, 導致其之后的值都是一樣的, 合理的做法是def log(message, when=None)來描述其行為。 同理def decode(data, default=None)也是。
- 用只能以關鍵字指定的參數保證代碼清晰, 如def safe_division_c(number, divisor, ignore_overflow=False, ignore_zero_division=False). 這個函數在python3可以做如下的改進def safe_division_c(number, divisor, *, ignore_overflow=False, ignore_zero_division=False), 通過加一個*, 使得函數的調用者必須指定ignore_overflow, ignore_zero_division的值, 保證了調用者清楚明確這個函數的功能。(*的功能是, 在其之前的參數可以用位置參數賦值, 在之后的參數只能用關鍵字參數賦值)。 而在python2中, 只能用**這種數量可變的關鍵字參數來達到上述的功能, def safe_division_d(number, divisor, **kwargs):
ignore_overflow = kwargs.pop(‘ignore_overflow’, False)
ignore_zero_div = kwargs.pop(‘ignore_zero_division’, False)
if kwargs:
raise TypeError(‘Unexpected **kwargs: %r’ % kwargs)
類與繼承
- 盡量使用輔助類來維護程序狀態而不是字典和元組。 另外collections的namedtuple可以定義精簡不可變的數據類, 如Point = namedtuple("Point", ("x", "y")). 實例化的時候可以是p = Point(1, 2), 這樣很簡潔。
- 對python簡單的接口來說, 應該直接傳入函數就好, 而不是傳入類的實例, 通過類的call方法, 類可以像函數一樣得到調用。盡量用帶call的函數來保存狀態, 而不是閉包里面的nonlocal變量
- @classmethod多態的構建對象. 它是通過類層面來構建的, 方法是def method(cls, xx), 而不是類通常的self, 優點是與構造器init相仿, 但不用重復的創建實例來產生數據, 子類都繼承了它, 使主類更加的generic。
- 使用super()來繼承父類, 若直接調用父類的init方法可造成程序混亂。
- 盡量避免多重繼承, 使用mix-in
- 多用public, 少用private(_), 使用protected()即可。
- 類其實都可以算是容器, 因此類也可以繼承list, tuple, set, dictionary以獲得這些容器的功能。編寫自己的容器的時候可以從collections.abc模塊繼承相應的抽象基類, 確保我們的類具有適當的接口及行為。