茴香豆的“茴”有四種寫法,Python的格式化字符串也有


茴香豆的“茴”有四種寫法,Python的格式化字符串也有

最近正在閱讀《Python Tricks: The Book》這本書,想要通過掌握書中提及的知識點提高自己的Python編程能力。本文主要記錄在學習該書第二章“Patterns for Cleaner Python”過程中的一些心得體會。

被低估的斷言

斷言是編程語言提供的一種調試工具,是讓程序在運行時進行自檢的代碼,可以輔助程序員進行交流和調試。程序員可以通過斷言了解代碼正確運行所依賴的假設,此外,當斷言為假時,程序員可以快速排查出由於輸入(輸入參數的取值范圍不符合預期)輸出(返回的結果是沒有意義的)不符合接口假設而導致的錯誤。

斷言常用於驗證以下兩類條件是否滿足:

  • 前置條件,調用方(Caller)在調用函數或類時必須滿足的條件,比如平方根運算要求被開方數必須大於0;
  • 后置條件,被調用方(Callee)的執行結果需要滿足的條件,比如商品打折后的價格不能高於原價或者低於0。

在Python中通過“assert expression ["," expression]”的方式聲明斷言,例如:

def apply_discount(product, discount):
    price = int(product['price'] * (1.0 - discount))
    # 驗證后置條件:打折后的價格應該介於0到原價之間
    assert 0 <= price <= product['price']
    return price

注意:斷言主要用於處理代碼中不應發生的錯誤,而那些在預期中的可能發生的錯誤建議使用異常進行處理。

多一個逗號,少一點糟心事

在定義列表、元組、字典以及集合時,在最后一個元素后面追加一個額外的逗號非常有用:

  • 增刪元素或調整元素的順序將變得容易,不會因為忘了逗號而導致錯誤;
  • 使得Git這樣的軟件配置管理工具可以准確追蹤到代碼的更改(git diff,改了哪一行就顯示哪一行,新增元素時不會因為在上一行的末尾加了個逗號,把上一行也標綠)。
# 新增元素'Jane',由於忘了在'Dilbert'后面加逗號,names變成了['Alice', 'Bob', 'DilbertJane']
names = [
    'Alice',
    'Bob',
    'Dilbert'
    'Jane'
]

上下文管理器和with語句

with語句解構了try/finally語句的標准用法:with語句開始執行時,會調用上下文管理器對象的“__enter__”方法,這對應try/finally語句之前申請系統資源的過程(比如打開文件);with語句執行結束后,會調用上下文管理器對象的“__exit__”方法,這對應finally子句中釋放系統資源的過程(比如關閉文件句柄)。

可以通過兩種方式定義上下文管理器:

  1. 通過定義類的方式,實現“__enter__”和“__exit__”方法;
  2. 使用contextlib模塊的contextmanager裝飾器,利用yield語句解構try/finally。
class Indenter:
    """縮進管理器"""
    def __init__(self):
        self.level = 0
    
    def __enter__(self):
        self.level += 1
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.level -= 1

    def print(self, text: str):
        print('    ' * self.level + text)

with Indenter() as indent:
    indent.print('風在吼')
    indent.print('馬在叫')
    with indent:
        indent.print('黃河在咆哮')
        indent.print('黃河在咆哮')
        with indent:
            indent.print('河西山岡萬丈高')
            indent.print('河東河北高粱熟了')
    indent.print('...')
    indent.print('保衛家鄉!保衛黃河!')
    indent.print('保衛華北!保衛全中國!')

#    風在吼
#    馬在叫
#        黃河在咆哮
#        黃河在咆哮
#            河西山岡萬丈高
#            河東河北高粱熟了
#    ...
#    保衛家鄉!保衛黃河!
#    保衛華北!保衛全中國!

(有沒有更好的寫法,感覺使用全局變量不太優雅?)

import contextlib

level = 0

@contextlib.contextmanager
def indenter():
    global level
    level += 1
    yield
    level -= 1

def cprint(text: str):
    print('    ' * level + text)

with indenter():
    cprint('風在吼')
    cprint('馬在叫')
    with indenter():
        cprint('黃河在咆哮')
        cprint('黃河在咆哮')
        with indenter():
            cprint('河西山岡萬丈高')
            cprint('河東河北高粱熟了')
    cprint('...')
    cprint('保衛家鄉!保衛黃河!')
    cprint('保衛華北!保衛全中國!')

作為前綴和后綴的下划線

在Python的變量名或方法名的前面或后面使用單個下划線或兩個下划線,有着不同的含義:

  1. 單個下划線作為前綴(_var)表示類或模塊的私有成員,嘗試通過通配符導入模塊的所有函數和變量時(from module import *),私有成員不會被導入;

  2. 單個下划線作為后綴(var_)用於與Python關鍵字進行區分,比如“def make_object(name, class_)”中的形參”class_”;

  3. 兩個下划線作為前綴(__var)用於避免與子類相同名稱的類變量之間的沖突,變量名會被Python解釋器重寫為“_類名__var”;

    class Test:
        """父類"""
        def __init__(self):
            self.foo = 0
            self.__bar = 1
    
    test = Test()
    dir(test)  # ['_Test__bar', 'foo', ...]
    
    class ExtendedTest(Test):
        """子類"""
        def __init__(self):
            super().__init__()
            self.foo = 3
            self.__bar = 4
    
    test = ExtendedTest()
    dir(test)  # ['_ExtendedTest__bar', '_Test__bar', 'foo', ...]
    
  4. 兩個下划線同時作為前綴和后綴(__var__)表示特殊方法;

  5. 單獨的下划線表示臨時變量的名稱(主要用於拆包時進行占位),也表示REPL最近一個表達式的結果。

茴香豆的“茴”有四種寫法,格式化字符串也有

在Python中有四種常用的格式化字符串的方法:

  1. printf風格的格式化,比如“print('逐夢演藝%s' % '圈圈圈圈圈')”;

  2. str.format,比如“print('逐夢演藝{0}{0}{0}{0}{0}'.format('圈'))”;

  3. f-string字符串插值:

    echoes = '圈'
    print(f'逐夢演藝{echoes * 5}')
    
  4. string.Template:

    import string
    
    template = string.Template('逐夢演藝${echoes}')
    print(template.substitute(echoes='圈圈圈圈圈'))
    

當需要對用戶輸入的字符串進行格式化時,推薦使用string.Template而非其他方案,因為惡意用戶可能通過類似SQL注入的方式獲取系統的敏感信息:

import string

SECRET = '這是私鑰'

class Error:
    def __init__(self):
        pass

err = Error()
user_input = '{error.__init__.__globals__[SECRET]}'
user_input.format(error=err)  # '這是私鑰',糟糕,私鑰被泄露了!

user_input = '${error.__init__.__globals__[SECRET]}'
string.Template(user_input).substitute(error=err)  # ValueError

Python之禪

Beautiful is better than ugly.
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.
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.
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!

參考資料

  1. Python Tricks: The Book
  2. 什么時候用異常,什么時候用斷言?
  3. 《重構:改善既有代碼的設計》,9.8節“引入斷言”
  4. 《代碼大全-第二版》,8.2節“斷言”
  5. Why are trailing commas allowed in a list?
  6. 《流暢的Python》,15.2節“上下文管理器和with塊”


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM