3.1 指令碼
在該部分,我們將深入研究編寫 Python 指令碼的慣例。
什麼是指令碼?指令碼就是執行和終止一系列語句的程式。
# program.pystatement1statement2statement3...
到目前為止,我們主要在編寫指令碼。
問題如果你編寫一個有用的指令碼,它的特性和功能將會增加。你可能想要將其應用於相關的問題。隨著時間的推移,它可能會成為一個關鍵的應用程式。如果你不注意的話,它可能會變成一團亂麻。因此,讓我們有條理的組織程式吧。
定義變數名稱必須在使用之前定義。
def square(x): return x*xa = 42b = a + 2 # Requires that `a` is definedz = square(b) # Requires `square` and `b` to be defined
順序很重要。
幾乎總是把變數和函式的定義放到頂部附近。
定義函式把所有與單個任務相關的程式碼都放到一個地方是個好主意。可以使用函式實現:
def read_prices(filename): prices = {} with open(filename) as f: f_csv = csv.reader(f) for row in f_csv: prices[row[0]] = float(row[1]) return prices
函式也可以簡化重複的操作。
oldprices = read_prices('oldprices.csv')newprices = read_prices('newprices.csv')
什麼是函式?函式是命名的語句序列。
def funcname(args): statement statement ... return result
任何 Python 語句都可以在函式內部使用。
def foo(): import math print(math.sqrt(2)) help(math)
Python 中沒有特殊語句(這使它很容易記住)。
函式定義可以按任何順序定義函式。
def foo(x): bar(x)def bar(x): statements# ORdef bar(x): statementsdef foo(x): bar(x)
在程式執行期間,函式必須在實際使用之前(呼叫)定義。
foo(3) # foo must be defined already
在文體上,函式以自底向上的方式定義可能更常見。
自底向上的風格函式被當做構建塊。較小/較簡單的塊優先。
# myprogram.pydef foo(x): ...def bar(x): ... foo(x) # Defined above ...def spam(x): ... bar(x) # Defined above ...spam(42) # Code that uses the functions appears at the end
後面的函式基於前面的函式構建。再次說明,這僅僅是一種風格問題。在上面程式中唯一重要的事情是 spam(42) 的呼叫是在最後一步。
函式設計理想情況下,函式應該是一個黑盒。它們應該僅對輸入進行操作,並避免全域性變數和奇怪的副作用。首要目標:模組化和可預測性。
文件字串以文件字串(doc-string)的形式包含文件是良好的實踐。文件字串是緊接著函式名的字串。它們用於 help() 函式,整合開發環境和其它的工具。
def read_prices(filename): ''' Read prices from a CSV file of name,price data ''' prices = {} with open(filename) as f: f_csv = csv.reader(f) for row in f_csv: prices[row[0]] = float(row[1]) return prices
一個好的文件字串實踐是寫一句簡短的話總結該函式做什麼。如果需要更多的資訊,請包含一個簡短的帶有更詳細的引數說明的使用示例,
型別註解也可以新增可選的型別提示到函式定義中。
def read_prices(filename: str) -> dict: ''' Read prices from a CSV file of name,price data ''' prices = {} with open(filename) as f: f_csv = csv.reader(f) for row in f_csv: prices[row[0]] = float(row[1]) return prices
提示在操作上什麼也不做。它們純粹是資訊性的。但是,整合開發工具,程式碼檢查器,以及其它工具可能會使用它來執行更多的操作。
練習在第 2 節中,編寫了一個名為 report.py的程式,該程式可以打印出顯示股票投資組合績效的報告。此程式包含一些函式。例如:
# report.pyimport csvdef read_portfolio(filename): ''' Read a stock portfolio file into a list of dictionaries with keys name, shares, and price. ''' portfolio = [] with open(filename) as f: rows = csv.reader(f) headers = next(rows) for row in rows: record = dict(zip(headers, row)) stock = { 'name' : record['name'], 'shares' : int(record['shares']), 'price' : float(record['price']) } portfolio.append(stock) return portfolio...
但是,程式的有些部分僅執行一系列的指令碼計算。這些程式碼出現在程式結尾處。例如:
...# Output the reportheaders = ('Name', 'Shares', 'Price', 'Change')print('%10s %10s %10s %10s' % headers)print(('-' * 10 + ' ') * len(headers))for row in report: print('%10s %10d %10.2f %10.2f' % row)...
在本練習中,我們使用函式來對該程式進行有條理的組織,使程式更健壯。
練習 3.1:將程式構造為函式的集合請修改 report.py 程式,以便所有主要操作(包括計算和輸出)都由一組函式執行。特別地:
建立列印報告的函式 print_report(report)。修改程式的最後一部分,使其僅是一系列函式呼叫,而無需進行其它運算。練習 3.2:為程式執行建立一個頂層函式把程式的最後一部分打包到單個函式 portfolio_report(portfolio_filename, prices_filename) 中。讓程式執行,以便下面的函式呼叫像之前一樣建立報告。
portfolio_report('Data/portfolio.csv', 'Data/prices.csv')
在最終版本中,程式只不過是一系列函式定義,最後是對單個函式portfolio_report() 的呼叫(它執行程式中涉及的所有步驟)。
透過將程式轉換為單個函式,在不同的輸入後可以很輕鬆地執行它。例如,在執行程式後以互動方式嘗試這些語句:
>>> portfolio_report('Data/portfolio2.csv', 'Data/prices.csv')... look at the output ...>>> files = ['Data/portfolio.csv', 'Data/portfolio2.csv']>>> for name in files: print(f'{name:-^43s}') portfolio_report(name, 'Data/prices.csv') print()... look at the output ...>>>
說明Python 使在有一系列語句的檔案中編寫相對無結構的指令碼變得很輕鬆。總體來說,無論何時,儘可能地利用函式通常總是更好的選擇。在某些時候,指令碼會不斷增加,並且我們希望它更有組織。另外,一個鮮為人知的事實是,如果使用函式,Python 的執行會更快一些。