翻譯:《實用的Python編程》03_01_Script
目錄 | 上一節 (2.7 對象模型) | 下一節 (3.2 深入函數)
3.1 腳本
在該部分,我們將深入研究編寫 Python 腳本的慣例。
什么是腳本?
腳本就是運行和終止一系列語句的程序。
# program.py
statement1
statement2
statement3
...
到目前為止,我們主要在編寫腳本。
問題
如果你編寫一個有用的腳本,它的特性和功能將會增加。你可能想要將其應用于相關的問題。隨著時間的推移,它可能會成為一個關鍵的應用程序。如果你不注意的話,它可能會變成一團亂麻。因此,讓我們有條理的組織程序吧。
定義變量
名稱必須在使用之前定義。
def square(x):
return x*x
a = 42
b = a + 2 # Requires that `a` is defined
z = 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
# OR
def bar(x):
statements
def foo(x):
bar(x)
在程序執行期間,函數必須在實際使用之前(調用)定義。
foo(3) # foo must be defined already
在文體上,函數以自底向上的方式定義可能更常見。
自底向上的風格
函數被當做構建塊。較小/較簡單的塊優先。
# myprogram.py
def 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.py
import csv
def 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 report
headers = ('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 的運行會更快一些。
浙公網安備 33010602011771號