首頁»Python»改善 Python 程序的 91 個建議(二)

                    改善 Python 程序的 91 個建議(二)

                    來源:馭風者 發布時間:2017-05-17 閱讀次數:

                    建議 24:遵循異常處理的幾點基本原則

                    異常處理的幾點原則:

                    1. 注意異常的粒度,不推薦在 try 中放入過多的代碼

                    2. 謹慎使用單獨的 except 語句處理所有異常,最好能定位具體的異常

                    3. 注意異常捕獲的順序,在適合的層次處理異常,Python 是按內建異常類的繼承結構處理異常的,所以推薦的做法是將繼承結構中子類異常在前拋出,父類異常在后拋出

                    4. 使用更為友好的異常信息,遵守異常參數的規范

                    建議 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:慎用變長參數

                    原因如下:

                    1. 使用過于靈活,導致函數簽名不夠清晰,存在多種調用方式

                    2. 使用*args和**kw簡化函數定義就意味著函數可以有更好的實現方法

                    使用場景:

                    1. 為函數添加一個裝飾器

                    2. 參數數目不確定

                    3. 實現函數的多態或子類需要調用父類的某些方法時

                    建議 34:深入理解 str() 和repr() 的區別

                    總結幾點:

                    1. str()面向用戶,返回用戶友好和可讀性強的字符串類型;repr()面向 Python 解釋器或開發人員,返回 Python 解釋器內部的含義

                    2. 解釋器中輸入a默認調用repr(),而print(a)默認調用str()

                    3. repr()返回值一般可以用eval()還原對象:obj == eval(repr(obj))

                    4. 以上兩個方法分別調用內建的__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)[email protected]%(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'))
                    
                    QQ群:WEB開發者官方群(515171538),驗證消息:10000
                    微信群:加小編微信 849023636 邀請您加入,驗證消息:10000
                    提示:更多精彩內容關注微信公眾號:全棧開發者中心(fsder-com)
                    網友評論(共0條評論) 正在載入評論......
                    理智評論文明上網,拒絕惡意謾罵 發表評論 / 共0條評論
                    登錄會員中心
                    福彩试机号今天 足球胜分差什么意思 排列五和值走势图300期 大乐透官网app 体彩6+1玩法介绍 山东十一运夺金预测 pk10计划 湖北30选5开奖今天的 重庆快乐十分一月开奖 陕西11选5推荐号 喜乐彩开奖号 九龙六合图库2013 足彩胜负彩360 体彩20选5号码走势 平刷王广东11选5 甘肃十一选五的走势图手机版