1. 程式人生 > >摘選改善Python程式的91個建議

摘選改善Python程式的91個建議

1、理解Pythonic概念

Pythonic

Tim Peters 的 《The Zen of Python》相信學過 Python 的都耳熟能詳,在互動式環境中輸入import this可以檢視,其實有意思的是這段 Python 之禪的原始碼:

d = {}
for c in (65, 97):
    for i in range(26):
        d[chr(i+c)] = chr((i+13) % 26 + c)
 
print "".join([d.get(c, c) for c in s])

書中還舉了一個快排的例子:

def quicksort(array):
    less 
= [] greater = [] if len(array) <= 1: return array pivot =array.pop() for x in array: if x <= pivot: less.append(x) else: greater.append(x) return quicksort(less) + [pivot] + quicksort(greater)

8、利用assert語句來發現問題

>>> y = 2
>>> assert
x == y, "not equals" Traceback (most recent call last): File "<stdin>", line 1, in <module> AssertionError: not equals >>> x = 1 >>> y = 2 # 以上程式碼相當於 >>> if __debug__ and not x == y: ... raise AssertionError("not equals") ... Traceback (most recent call last): File
"<stdin>", line 2, in <module> AssertionError: not equals

執行時加入-O引數可以禁用斷言。

 9、資料交換不推薦使用中間變數
x, y = 1, 2

x, y = y, x

原理:右值建立元組,左值接收元組的對應元素。

10、充分利用Lazy evaluation 的特性

def fib():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

11、理解列舉替代實現的缺陷

  利用Python的動態特性可以實現列舉:

# 方式一
class Seasons:
    Spring, Summer, Autumn, Winter = range(4)
# 方式二
def enum(*posarg, **keysarg):
    return type("Enum", (object,), dict(zip(posarg, range(len(posarg))), **keysarg))
Seasons = enum("Spring", "Summer", "Autumn", Winter=1)
Seasons.Spring
# 方式三
>>> from collections import namedtuple
>>> Seasons = namedtuple('Seasons', 'Spring Summer Autumn Winter')._make(range(4))
>>> Seasons.Spring
0
# 但通過以上方式實現列舉都有不合理的地方
>>> Seasons._replace(Spring=2)                                             │
Seasons(Spring=2, Summer=1, Autumn=2, Winter=3)  
# Python3.4 中加入了列舉,僅在父類沒有任何列舉成員的時候才允許繼承
View Code

12、不推薦使用type來進行型別檢查、而使用isinstance()

 

14、警惕eval()的安全漏洞

# 合理正確地使用
>>> eval("1+1==2")
True
>>> eval('"a"+"b"')
'ab'
# 壞心眼的geek
>>> eval('__import__("os").system("dir")')
Desktop  Documents  Downloads  examples.desktop  Music  Pictures  Public  __pycache__  Templates  Videos
0
>>> eval('__import__("os").system("del * /Q")')     # 嘿嘿嘿
View Code

  如果確實需要使用eval,建議使用安全性更好的ast.literal_eval。

 

19、有節制地使用from ... import 語句
Python 提供三種方式來引入外部模組:import語句、from...import語句以及__import__函式,其中__import__函式顯式地將模組的名稱作為字串傳遞並賦值給名稱空間的變數。

使用import需要注意以下幾點:

優先使用import a的形式

有節制地使用from a import A

儘量避免使用from a import *

為什麼呢?我們來看看 Python 的 import 機制,Python 在初始化執行環境的時候會預先載入一批內建模組到記憶體中,同時將相關資訊存放在sys.modules中,我們可以通過sys.modules.items()檢視預載入的模組資訊,當載入一個模組時,直譯器實際上完成了如下動作:

在sys.modules中搜索該模組是否存在,如果存在就匯入到當前區域性名稱空間,如果不存在就為其建立一個字典物件,插入到sys.modules中

載入前確認是否需要對模組對應的檔案進行編譯,如果需要則先進行編譯

執行動態載入,在當前名稱空間中執行編譯後的位元組碼,並將其中所有的物件放入模組對應的字典中
View Code
>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__']
>>> import test
testing module import
>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'test']
>>> import sys
>>> 'test' in sys.modules.keys()
True
>>> id(test)
140367239464744
>>> id(sys.modules['test'])
140367239464744
>>> dir(test)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'a', 'b']
>>> sys.modules['test'].__dict__.keys()
dict_keys(['__file__', '__builtins__', '__doc__', '__loader__', '__package__', '__spec__', '__name__', 'b', 'a', '__cached__'])
View Code

從上可以看出,對於使用者自定義的模組,import 機制會建立一個新的 module 將其加入當前的區域性名稱空間中,同時在 sys.modules 也加入該模組的資訊,但本質上是在引用同一個物件,通過test.py所在的目錄會多一個位元組碼檔案。

 

20、優先使用absolute import 來匯入模組

23、使用else子句簡化迴圈(處理異常)

Python 的 else 子句提供了隱含的對迴圈是否由 break 語句引發迴圈結束的判斷

>>> def print_prime(n):
...     for i in range(2, n):
...         for j in range(2, i):
...             if i % j == 0:
...                 break
...         else:
...             print('{} is a prime number'.format(i))
... 
>>> print_prime(7)
2 is a prime number
3 is a prime number
5 is a prime number
View Code

可以看出,else 子句在迴圈正常結束和迴圈條件不成立時被執行,由 break 語句中斷時不執行,同樣,我們可以利用這顆語法糖作用在 while 和 try...except 中。

31、記住函式傳參既不是傳值也不是引用

  正確的說法是傳物件(call by object)或傳物件的引用(call-by-object-reference),函式引數在傳遞過程中將整個物件傳入,對可變物件的修改在函式外部以及內部都可見,對不可變物件的”修改“往往是通過生成一個新物件然是賦值實現的。

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})
View Code

  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或為負
View Code

41、使用 argparse 處理命令列引數

import argparse
parse = argparse.ArgumentParser()
parse.add_argument('-o', '--output')
parse.add_argument('-v', dest='verbose', action='store_true')
args = parser.parse_args()
View Code

42、使用pandas處理大型CSV檔案   教程

reader(csvfile[, dialect='excel'][, fmtparam])  # 讀取一個 csv 檔案,返回一個 reader 物件
csv.writer(csvfile, dialect='excel', **fmtparams) # 寫入 csv 檔案
csv.DictWriter(csvfile, fieldnames, restval='', extrasaction='raise', dialect='excel')
常用API

43、一般情況下使用 ElementTree 解析 XML     教程

count = 0
for event, elem in ET.iterparse('test.xml'):
    if event == 'end':
        if elem.tag == 'userid':
            count += 1
    elem.clear()
print(count)
View Code

45、使用 traceback 獲取棧資訊

  當發生異常,開發人員往往需要看到現場資訊,trackback 模組可以滿足這個需求

traceback.print_exc()   # 列印錯誤型別、值和具體的trace資訊
traceback.print_exception(type, value, traceback[, limit[, file]])  # 前三個引數的值可以從sys.exc_info()
raceback.print_exc([limit[, file]])         # 同上,不需要傳入那麼多引數
traceback.format_exc([limit])               # 同 print_exc(),返回的是字串
traceback.extract_stack([file, [, limit]])  # 從當前棧中提取 trace 資訊
View Code

  traceback 模組獲取異常相關的資料是通過sys.exc_info()得到的,該函式返回異常型別type、異常value、呼叫和堆疊資訊traceback組成的元組。

同時 inspect 模組也提供了獲取 traceback 物件的介面。

50、利用模組實現單例模式

51、用 mixin 模式讓程式更加靈活

  模板方法模式就是在一個方法中定義一個演算法的骨架,並將一些實現步驟延遲到子類中。模板方法可以使子類在不改變演算法結構的情況下,重新定義演算法中的某些步驟。看個例子:

class People(object):
    def make_tea(self):
        teapot = self.get_teapot()
        teapot.put_in_tea()
        teapot.put_in_water()
        return teapot
View Code

  顯然get_teapot()方法並不需要預先定義,也就是說我們的基類不需要預先申明抽象方法,子類只需要繼承 People 類並實現get_teapot(),這給除錯程式碼帶來了便利。但我們又想到如果一個子類 StreetPeople 描述的是正走在街上的人,那這個類將不會實現get_teapot(),一呼叫make_tea()就會產生找不到get_teapot()的 AttributeError,所以此時程式設計師應該立馬想到,隨著需求的增多,越來越多的 People 子類會選擇不喝茶而喝咖啡,或者是抽雪茄之類的,按照以上的思路,我們的程式碼只會變得越發難以維護。

  所以我們希望能夠動態生成不同的例項:

class UseSimpleTeapot(object):
    def get_teapot(self):
        return SimpleTeapot()

class UseKungfuTeapot(object):
    def get_teapot(self):
        return KungfuTeapot()

class OfficePeople(People, UseSimpleTeapot): pass

class HomePeople(People, UseSimpleTeapot): pass

class Boss(People, UseKungfuTeapot): pass

def simple_tea_people():
    people = People()
    people.__base__ += (UseSimpleTeapot,)
    return people

def coffee_people():
    people = People()
    people.__base__ += (UseCoffeepot,)

def tea_and_coffee_people():
    people = People()
    people.__base__ += (UseSimpleTeapot, UserCoffeepot,)
    return people

def boss():
    people = People()
    people.__base__ += (KungfuTeapot, UseCoffeepot, )
    return people
View Code

  以上程式碼的原理在於每個類都有一個__bases__屬性,它是一個元組,用來存放所有的基類,作為動態語言,Python 中的基類可以在執行中可以動態改變。所以當我們向其中增加新的基類時,這個類就擁有了新的方法,這就是混入mixin。

  利用這個技術我們可以在不修改程式碼的情況下就可以完成需求:
import mixins   # 把員工需求定義在 Mixin 中放在 mixins 模組

def staff():
    people = People()
    bases = []
    for i in config.checked():
        bases.append(getattr(maxins, i))
    people.__base__ += tuple(bases)
    return people
View Code

52、用釋出訂閱模式實現鬆耦合

  釋出訂閱模式是一種程式設計模式,訊息的傳送者不會發送其訊息給特定的接收者,而是將釋出的訊息分為不同的類別直接釋出,並不關注訂閱者是誰。而訂閱者可以對一個或多個類別感興趣,且只接收感興趣的訊息,並且不關注是哪個釋出者釋出的訊息。要實現這個模式,就需要一箇中間代理人 Broker,它維護著釋出者和訂閱者的關係,訂閱者把感興趣的主題告訴它,而釋出者的資訊也通過它路由到各個訂閱者處。

from collections import defaultdict
route_table = defaultdict(list)
def sub(topic, callback):
    if callback in route_table[topic]:
        return
    route_table[topic].append(callback)

def pub(topic, *args, **kw):
    for func in route_table[topic]:
        func(*args, **kw)
Broker.py

  將以上程式碼放在 Broker.py 的模組,省去了各種引數檢測、優先處理、取消訂閱的需求,只向我們展示釋出訂閱模式的基礎實現:

import Broker
def greeting(name):
    print('Hello, {}'.format(name))
Broker.sub('greet', greeting)
Broker.pub('greet', 'LaiYonghao')
View Code

  注意學習 blinker 和 python-message 兩個模組。

 

                              a