目錄 | 上一節 (3.1 腳本) | [下一節 (3.3 錯誤檢查)]()python
儘管函數在早先時候介紹了,但有關函數在更深層次上是如何工做的細節卻不多提供。本節旨在填補這些空白,並討論函數調用約定,做用域規則等問題。git
考慮如下函數:github
def read_prices(filename, debug): ...
可使用位置參數調用該函數:segmentfault
prices = read_prices('prices.csv', True)
或者,可使用關鍵字參數調用該函數:app
prices = read_prices(filename='prices.csv', debug=True)
有時候,你但願參數是可選的,若是是這樣,請在函數定義中分配一個默認值。ide
def read_prices(filename, debug=False): ...
若是分配了默認值,則參數在函數調用中是可選的。函數
d = read_prices('prices.csv') e = read_prices('prices.dat', True)
注意:帶有默認值的參數(譯註:即關鍵字參數)必須出如今參數列表的末尾(全部非可選參數都放在最前面)工具
比較如下兩種不一樣的調用風格:開發工具
parse_data(data, False, True) # ????? parse_data(data, ignore_errors=True) parse_data(data, debug=True) parse_data(data, debug=True, ignore_errors=True)
在大部分狀況下,關鍵字參數提升了代碼的簡潔性——特別是對於用做標誌的參數,或者與可選特性相關的參數。ui
始終爲函數參數指定簡短但有意義的名稱。
使用函數的人可能想要使用關鍵字調用風格。
d = read_prices('prices.csv', debug=True)
Python 開發工具將會在幫助功能或者幫助文檔中顯示這些名稱。
return
語句返回一個值:
def square(x): return x * x
若是沒有給出返回值或者 return
語句缺失,那麼返回 None
:
def bar(x): statements return a = bar(4) # a = None # OR def foo(x): statements # No `return` b = foo(4) # b = None
函數只能返回一個值。可是,經過將返回值放到元組中,函數能夠返回多個值:
def divide(a,b): q = a // b # Quotient r = a % b # Remainder return q, r # Return a tuple
用例:
x, y = divide(37,5) # x = 7, y = 2 x = divide(37, 5) # x = (7, 2)
程序給變量賦值:
x = value # Global variable def foo(): y = value # Local variable
變量賦值發生在函數的內部和外部。定義在函數外部的變量是「全局的」。定義在函數內部的變量是「局部的」。
在函數內部賦值的變量是私有的。
def read_portfolio(filename): portfolio = [] for line in open(filename): fields = line.split(',') s = (fields[0], int(fields[1]), float(fields[2])) portfolio.append(s) return portfolio
在此示例中,filename
, portfolio
, line
, fields
和 s
是局部變量。在函數調用以後,這些變量將不會保留或者不可訪問。
>>> stocks = read_portfolio('portfolio.csv') >>> fields Traceback (most recent call last): File "<stdin>", line 1, in ? NameError: name 'fields' is not defined >>>
局部變量也不能與其它地方的變量衝突。
函數能夠自由地訪問定義在同一文件中的全局變量值。
name = 'Dave' def greeting(): print('Hello', name) # Using `name` global variable
可是,函數不能修改全局變量:
name = 'Dave' def spam(): name = 'Guido' spam() print(name) # prints 'Dave'
切記:函數中的全部賦值都是局部的
若是必須修改全局變量,請像下面這樣聲明它:
name = 'Dave' def spam(): global name name = 'Guido' # Changes the global name above
全局聲明必須在使用以前出現,而且相應的變量必須與該函數處在同一文件中。看上面這個函數,要知道這是一種糟糕的形式。事實上,若是能夠的話,儘可能避免使用 global
。若是須要一個函數來修改函數外部的某種狀態,最好是使用類來代替(稍後詳細介紹)。
當調用一個函數的時候,參數變量的傳遞是引用傳遞。不拷貝值(參見2.7 節)。若是傳遞了可變數據類型(如列表,字典),它們能夠被原地修改。
def foo(items): items.append(42) # Modifies the input object a = [1, 2, 3] foo(a) print(a) # [1, 2, 3, 42]
關鍵點:函數不接收輸入參數的拷貝。
確保瞭解修改值與給變量名從新賦值的細微差異。
def foo(items): items.append(42) # Modifies the input object a = [1, 2, 3] foo(a) print(a) # [1, 2, 3, 42] # VS def bar(items): items = [4,5,6] # Changes local `items` variable to point to a different object b = [1, 2, 3] bar(b) print(b) # [1, 2, 3]
提醒:變量賦值永遠不會重寫內存。名稱只是被綁定到了新的值上面
本組練習實現的內容多是本課程最強大的和最難的。有不少步驟,而且過去練習中的許多概念被一次性整合在一塊兒。雖然最後的題解只有大約 25 行的代碼,但要花點時間,確保你理解每個部分。
report.py
的中心部分主要用於讀取 CSV 文件。例如,read_portfolio()
函數讀取包含投資組合數據的文件,read_prices()
函數讀取包含價格數據的文件。在這兩個函數中,有不少底層的「精細的」事以及類似的特性。例如,它們都打開一個文件並使用 csv
模塊來處理,而且將各類字段轉換爲新的類型。
若是真的須要對大量的文件進行解析,可能須要清理其中的一些內容使其更通用。這是咱們的目標。
經過打開 Work/fileparse.py
文件開始本練習,該文件是咱們將要寫代碼的地方。
首先,讓咱們僅關注將 CSV 文件讀入字典列表的問題。在 fileparse.py
中,定義一個以下所示的函數:
# fileparse.py import csv def parse_csv(filename): ''' Parse a CSV file into a list of records ''' with open(filename) as f: rows = csv.reader(f) # Read the file headers headers = next(rows) records = [] for row in rows: if not row: # Skip rows with no data continue record = dict(zip(headers, row)) records.append(record) return records
該函數將 CSV 文件讀入字典列表中,可是隱藏了打開文件,使用 csv
模塊處理,忽略空行等詳細信息。
試試看:
提示: python3 -i fileparse.py
.
>>> portfolio = parse_csv('Data/portfolio.csv') >>> portfolio [{'price': '32.20', 'name': 'AA', 'shares': '100'}, {'price': '91.10', 'name': 'IBM', 'shares': '50'}, {'price': '83.44', 'name': 'CAT', 'shares': '150'}, {'price': '51.23', 'name': 'MSFT', 'shares': '200'}, {'price': '40.37', 'name': 'GE', 'shares': '95'}, {'price': '65.10', 'name': 'MSFT', 'shares': '50'}, {'price': '70.44', 'name': 'IBM', 'shares': '100'}] >>>
這很好,除了不能使用數據作任何有用的計算以外。由於全部的內容都是用字符串表示。咱們將立刻解決此問題,先讓咱們繼續在此基礎上進行構建。
在大部分狀況下,你只對 CSV 文件中選定的列感興趣,而不是全部數據。修改 parse_csv()
函數,以便讓用戶指定任意的列,以下所示:
>>> # Read all of the data >>> portfolio = parse_csv('Data/portfolio.csv') >>> portfolio [{'price': '32.20', 'name': 'AA', 'shares': '100'}, {'price': '91.10', 'name': 'IBM', 'shares': '50'}, {'price': '83.44', 'name': 'CAT', 'shares': '150'}, {'price': '51.23', 'name': 'MSFT', 'shares': '200'}, {'price': '40.37', 'name': 'GE', 'shares': '95'}, {'price': '65.10', 'name': 'MSFT', 'shares': '50'}, {'price': '70.44', 'name': 'IBM', 'shares': '100'}] >>> # Read only some of the data >>> shares_held = parse_csv('Data/portfolio.csv', select=['name','shares']) >>> shares_held [{'name': 'AA', 'shares': '100'}, {'name': 'IBM', 'shares': '50'}, {'name': 'CAT', 'shares': '150'}, {'name': 'MSFT', 'shares': '200'}, {'name': 'GE', 'shares': '95'}, {'name': 'MSFT', 'shares': '50'}, {'name': 'IBM', 'shares': '100'}] >>>
練習 2.23 中給出了列選擇器的示例。
然而,這裏有一個方法能夠作到這一點:
# fileparse.py import csv def parse_csv(filename, select=None): ''' Parse a CSV file into a list of records ''' with open(filename) as f: rows = csv.reader(f) # Read the file headers headers = next(rows) # If a column selector was given, find indices of the specified columns. # Also narrow the set of headers used for resulting dictionaries if select: indices = [headers.index(colname) for colname in select] headers = select else: indices = [] records = [] for row in rows: if not row: # Skip rows with no data continue # Filter the row if specific columns were selected if indices: row = [ row[index] for index in indices ] # Make a dictionary record = dict(zip(headers, row)) records.append(record) return records
這部分有一些棘手的問題,最重要的一個多是列選擇到行索引的映射。例如,假設輸入文件具備如下標題:
>>> headers = ['name', 'date', 'time', 'shares', 'price'] >>>
如今,假設選定的列以下:
>>> select = ['name', 'shares'] >>>
爲了執行正確的選擇,必須將選擇的列名映射到文件中的列索引。這就是該步驟正在執行的操做:
>>> indices = [headers.index(colname) for colname in select ] >>> indices [0, 3] >>>
換句話說,名稱("name" )是第 0 列,股份數目("shares" )是第 3 列。
當從文件讀取數據行的時候,使用索引對其進行過濾:
>>> row = ['AA', '6/11/2007', '9:50am', '100', '32.20' ] >>> row = [ row[index] for index in indices ] >>> row ['AA', '100'] >>>
修改 parse_csv()
函數,以即可以選擇將類型轉換應用到返回數據上。例如:
>>> portfolio = parse_csv('Data/portfolio.csv', types=[str, int, float]) >>> portfolio [{'price': 32.2, 'name': 'AA', 'shares': 100}, {'price': 91.1, 'name': 'IBM', 'shares': 50}, {'price': 83.44, 'name': 'CAT', 'shares': 150}, {'price': 51.23, 'name': 'MSFT', 'shares': 200}, {'price': 40.37, 'name': 'GE', 'shares': 95}, {'price': 65.1, 'name': 'MSFT', 'shares': 50}, {'price': 70.44, 'name': 'IBM', 'shares': 100}] >>> shares_held = parse_csv('Data/portfolio.csv', select=['name', 'shares'], types=[str, int]) >>> shares_held [{'name': 'AA', 'shares': 100}, {'name': 'IBM', 'shares': 50}, {'name': 'CAT', 'shares': 150}, {'name': 'MSFT', 'shares': 200}, {'name': 'GE', 'shares': 95}, {'name': 'MSFT', 'shares': 50}, {'name': 'IBM', 'shares': 100}] >>>
在 練習 2.24 中已經對此進行了探索。須要將下列代碼片斷插入到題解中:
... if types: row = [func(val) for func, val in zip(types, row) ] ...
某些 CSV 文件不包含任何的標題信息。例如,prices.csv
文件看起來像下面這樣:
"AA",9.22 "AXP",24.85 "BA",44.85 "BAC",11.27 ...
修改 parse_csv()
文件以便經過建立元組列表來處理此類文件。例如:
>>> prices = parse_csv('Data/prices.csv', types=[str,float], has_headers=False) >>> prices [('AA', 9.22), ('AXP', 24.85), ('BA', 44.85), ('BAC', 11.27), ('C', 3.72), ('CAT', 35.46), ('CVX', 66.67), ('DD', 28.47), ('DIS', 24.22), ('GE', 13.48), ('GM', 0.75), ('HD', 23.16), ('HPQ', 34.35), ('IBM', 106.28), ('INTC', 15.72), ('JNJ', 55.16), ('JPM', 36.9), ('KFT', 26.11), ('KO', 49.16), ('MCD', 58.99), ('MMM', 57.1), ('MRK', 27.58), ('MSFT', 20.89), ('PFE', 15.19), ('PG', 51.94), ('T', 24.79), ('UTX', 52.61), ('VZ', 29.26), ('WMT', 49.74), ('XOM', 69.35)] >>>
要執行此更改,須要修改代碼以便數據的第一行不被解釋爲標題行。另外,須要確保不建立字典,由於再也不有可用於列名的鍵。
儘管 CSV 文件很是廣泛,但還可能會遇到使用其它列分隔符(如 製表符(tab) 或空格符(space))的文件。例如,以下所示的 Data/portfolio.dat
文件:
name shares price "AA" 100 32.20 "IBM" 50 91.10 "CAT" 150 83.44 "MSFT" 200 51.23 "GE" 95 40.37 "MSFT" 50 65.10 "IBM" 100 70.44
csv.reader()
函數容許像下面這樣指定不一樣的分隔符:
rows = csv.reader(f, delimiter=' ')
修改 parse_csv()
函數以便也容許修改分隔符。
例如:
>>> portfolio = parse_csv('Data/portfolio.dat', types=[str, int, float], delimiter=' ') >>> portfolio [{'price': '32.20', 'name': 'AA', 'shares': '100'}, {'price': '91.10', 'name': 'IBM', 'shares': '50'}, {'price': '83.44', 'name': 'CAT', 'shares': '150'}, {'price': '51.23', 'name': 'MSFT', 'shares': '200'}, {'price': '40.37', 'name': 'GE', 'shares': '95'}, {'price': '65.10', 'name': 'MSFT', 'shares': '50'}, {'price': '70.44', 'name': 'IBM', 'shares': '100'}] >>>
到目前爲止,若是你已經完成,那麼你建立了一個很是有用的庫函數。你可使用它去解析任意的 CSV 文件,選擇感興趣的列,執行類型轉換,而不用對文件或者 csv
模塊的內部工做有太多的擔憂。
目錄 | 上一節 (3.1 腳本) | [下一節 (3.3 錯誤檢查)]()