官术网_书友最值得收藏!

  • Python數據分析基礎
  • (美)克林頓·布朗利
  • 19354字
  • 2020-05-06 16:34:58

1.4 Python語言基礎要素

既然你已經學會了如何創建和運行Python腳本,那么以前需要手動實現的業務過程,現在完全可以通過編寫Python腳本來自動化和規模化地完成。后面幾章會詳細介紹如何使用Python腳本來自動化和規模化地完成任務,但是在進行下一部分內容之前,還需要掌握更多的Python語言基礎要素。通過掌握更多的基礎要素,你會對Python有更深入的理解,在后面的章節中就可以綜合運用這些知識來完成具體的數據處理任務。首先,本節介紹Python中最常用的數據類型,然后討論使用if語句和函數來處理數據的方法。在此之后,從實際出發,介紹如何使用Python讀寫文本文件和CSV文件。

1.4.1 數值

Python有好幾種內置數值類型。數值類型非常有用,因為很多商業應用需要對數值進行分析和處理。Python中最主要的4種數值類型是整數、浮點數、長整數和復數。這里只介紹整數和浮點數,因為它們在商業應用中是最常用的。你可以把下面處理整數和浮點數的示例添加到first_script.py中,放在現有的代碼下面,然后重新運行腳本,在屏幕上檢查輸出。

1.整數

下面直接看幾個帶有整數的示例:

x = 9
print("Output #4: {0}".format(x))
print("Output #5: {0}".format(3**4))
print("Output #6: {0}".format(int(8.3)/int(2.7)))

Output #4展示了如何將一個整數(數字9)賦給變量x,然后將變量x打印出來。Output #5說明了如何得到3的4次方(等于81)并將結果打印出來。Output #6演示了將數值轉換成整數并進行除法運算的方法。數值通過內置的int函數轉換成整數,所以算式變成了8除以2,結果為4.0。

2.浮點數

和整數一樣,浮點數(即帶小數點的數)對很多商業應用來說也是非常重要的。下面是幾個帶有浮點數的示例:

print("Output #7: {0:.3f}".format(8.3/2.7))
y = 2.5*4.8
print("Output #8: {0:.1f}".format(y))
r = 8/float(3)
print("Output #9: {0:.2f}".format(r))
print("Output #10: {0:.4f}".format(8.0/3))

Output #7和Output #6非常相似,除了將兩個相除的數保留為浮點數,這樣算式就是8.3除以2.7,大約等于3.074。這個示例中print語句的語法,"{0:.3f}".format(floating_point_number/floating_point_number),說明了如何設置print語句中的小數位數。在這個示例中,.3f設定了打印的輸出值應該有3位小數。

Output #8表示用2.5乘以4.8,將結果賦給變量y,然后將結果打印出來,帶有一位小數。這兩個浮點數相乘的結果是12,所以打印出的值是12.0。Output #9和Output #10表示以兩種方式計算8除以3,結果都是一個浮點數,大約等于2.667。

type函數

Python提供一個名為type的函數,你可以對所有對象調用這個函數,來獲得關于Python如何處理這個對象的更多信息。如果你對一個數值變量調用這個函數,它會告訴你這個數值是整數還是浮點數,還會告訴你這個數值是否能當作字符串進行處理。函數的語法非常簡單:type(varible)會返回Python中的數據類型。此外,因為Python是一種“面向對象”的語言,所以你可以對Python中所有命名對象調用type函數,不僅是變量,還有函數、語句等。如果你的代碼出現了意外的錯誤,調用type函數可以幫助你進行錯誤診斷。

在Python中進行數值處理時,需要知道的非常重要的一點是,你可以使用幾種標準庫模塊和內置函數與模塊來進行常見的數學計算。你已經使用了兩個內置函數來處理數值,分別是int和float。另一個有用的標準模塊是math。

Python標準模塊隨著Python基本程序一起安裝在你的計算機中,但是當你新建一個腳本時,計算機只加載一些非常基本的操作(這就是Python啟動非常快的一個原因)。要想使用math模塊中的一些函數,只需在腳本開頭shebang行的下方添加from math import[function name]。例如,可以將下面的代碼行添加到first_script.py中,在shebang行下面:

#!/usr/bin/env python3
from math import exp, log, sqrt

如果在first_script.py中添加了這一行,你就有3個有用的數學函數可以任意使用了。函數exp、log和sqrt分別表示e的乘方、自然對數和平方根。下面是使用math模塊中的函數進行計算的幾個示例:

print("Output #11: {0:.4f}".format(exp(3)))
print("Output #12: {0:.2f}".format(log(4)))
print("Output #13: {0:.1f}".format(sqrt(81)))

這3種數學函數的計算結果是浮點數,分別約為20.0855、1.39和9.0。

這只是math模塊中一點微小的功能。Python中還有很多有用的數學函數和模塊,用于商業、科學、統計和其他應用,本書還會對此進行更多的討論。關于數學模塊和其他標準模塊以及內置函數的更多信-息,可以參考Python標準庫(https://docs.python.org/3/library/index.html)。

1.4.2 字符串

字符串是Python中的另一種基本數據類型。它通常是指人類可以閱讀的文本,你可以這么理解。但更廣泛地說,它是一個字符序列,并且字符只有在組成這個序列時才有意義。很多商業應用中都有字符串類型的數據,比如供應商和客戶的名字及地址、評價和反饋數據、事件日志和文檔記錄。一些對象看上去是整數,但實際上是字符串,比如郵政編碼。郵政編碼01111(馬薩諸塞州斯普林菲爾德)和整數1111是不一樣的,你不能對郵政編碼做加減乘除,所以最好在代碼中將郵政編碼作為字符串來處理。這一節將介紹用于字符串管理的一些模塊、函數和操作。

字符串可以包含在單引號、雙引號、3個單引號或3個雙引號之間。下面是字符串的幾個示例:

print("Output #14: {0:s}".format('I\'m enjoying learning Python.'))

print("Output #15: {0:s}".format("This is a long string. Without the backslash\
it would run off of the page on the right in the text editor and be very\
difficult to read and edit. By using the backslash you can split the long\
string into smaller strings on separate lines so that the whole string is easy\
to view in the text editor."))

print("Output #16: {0:s}".format('''You can use triple single quotes

for multi-line comment strings.'''))

print("Output #17: {0:s}".format("""You can also use triple double quotes

for multi-line comment strings."""))

Output #14和本章開頭的示例非常像。它展示了一個包含在單引號之間的簡單字符串。這個print語句的結果是"I'm enjoying learning Python."。請記住,如果用雙引號來包含這個字符串的話,就不需要在"I'm"的單引號前面使用反斜杠了。

Output #15展示了如何使用反斜杠將一個非常長的字符串分段顯示在多個行中,以使它易于理解和編輯。盡管這個字符串分布在腳本的多個行中,它還是一個字符串,并作為一個完整的字符串被打印出來。在這種將長字符串分成多行的方法中,要注意的是反斜杠必須是每行的最后一個字符。如果你意外地按了一下空格鍵,反斜杠后面就會出現一個看不見的空格,腳本就會拋出一個語法錯誤,不能正常運行。因此,使用3個單引號或3個雙引號來創建多行字符串更穩妥一些。

Output #16和Output #17展示了如何使用3個單引號和3個雙引號來創建多行字符串。這兩個示例的輸出如下:

Output #16: You can use triple single quotes
for multi-line comment strings.
Output #17: You can also use triple double quotes

for multi-line comment strings.

當你使用3個單引號或3個雙引號時,不需要在前面一行的末尾加上反斜杠。還有,請注意一下Output #15和Output #16以及Output #17在打印到屏幕之后的區別。Output #15使用在行尾添加反斜杠的方式將字符串分成多行,使每行代碼更短并且更易于理解,但還是作為一行長文本打印到屏幕上。與之相反,Output #16和Output #17使用3個單引號和3個雙引號創建多行字符串,打印到屏幕上后也是分行的。

和數值類型一樣,也有很多標準模塊、內置函數和操作符可以用來管理字符串。常用的操作符和函數包括+、*和len。下面就是幾個使用這些操作符處理字符串的示例:

string1 = "This is a "
string2 = "short string."
sentence = string1 + string2
print("Output #18: {0:s}".format(sentence))
print("Output #19: {0:s} {1:s}{2:s}".format("She is", "very "*4, "beautiful."))
m = len(sentence)
print("Output #20: {0:d}".format(m))

Output #18展示了如何使用+操作符來將兩個字符串相加。這個print語句的輸出結果是This is a short string。+操作符將兩個字符串按照原樣相加,所以如果你想在結果字符串中留出空格的話,就必須在原字符串中加上空格(例如:在Output #18中,要在字母“a”后面加空格)或者在原字符串之間加上空格(例如:在Output #19中,要在“very”后面加上空格)。

Output #19展示了如何使用*操作符將字符串重復一定的次數。在這個示例中,結果字符串包含了字符串“very”(就是very后面跟著一個空格)的4個副本。

Output #20展示了如何使用內置函數len來確定字符串中字符的數量。函數len將空格與標點符號也計入字符串長度。所以,在Output #20中,字符串This is a short string.的長度是23個字符。

處理字符串的一個常用標準庫模塊是string。在string模塊中,你可以使用多個函數來有效管理字符串。下面用示例說明了使用這些字符串函數的方法。

1.split

下面的兩個示例展示了如何使用split函數來將一個字符串拆分成一個子字符串列表,列表中的子字符串正好可以構成原字符串。(列表是Python中的另一種內置數據類型,本章后面將會討論。)split函數可以在括號中使用兩個附加參數。第一個附加參數表示使用哪個字符進行拆分。第二個附加參數表示進行拆分的次數(例如:進行兩次拆分,可以得到3個子字符串):

string1 = "My deliverable is due in May"
string1_list1 = string1.split()
string1_list2 = string1.split(" ",2)
print("Output #21: {0}".format(string1_list1))
print("Output #22: FIRST PIECE:{0} SECOND PIECE:{1} THIRD PIECE:{2}"\
.format(string1_list2[0], string1_list2[1], string1_list2[2]))
string2 = "Your,deliverable,is,due,in,June"
string2_list = string2.split(',')
print("Output #23: {0}".format(string2_list))
print("Output #24: {0} {1} {2}".format(string2_list[1], string2_list[5],\
string2_list[-1]))

在Output #21中,括號中沒有附加參數,所以split函數使用空格字符(默認值)對字符串進行拆分。因為這個字符串中有5個空格,所以字符串被拆分成具有6個子字符串的列表。新生成的列表為['My', 'deliverable', 'is', 'due', 'in', 'May']。

Output #22明確地在split函數中包含了兩個附加參數。第一個附加參數是" ",說明想用空格來拆分字符串。第二個附加參數是2,說明只想使用前兩個空格進行拆分。因為設定了拆分兩次,所以會生成一個帶有3個元素的列表。第二個附加參數會在你解析數據的時候派上用場。舉例來說,你可能會解析一個日志文件,文件中包含時間戳、錯誤代碼和由空格分隔的錯誤信息。在這種情況下,你應該使用前兩個空格進行拆分,解析出時間戳和錯誤代碼,但是不使用剩下的空格進行拆分,以便完整無缺地保留錯誤信息。

在Output #23和Output #24中,括號中的附加參數是個逗號。在這種情況下,split函數在出現逗號的位置拆分字符串。結果列表為['Your', 'deliverable', 'is', 'due', 'in', 'June']。

2.join

下面的示例展示了如何使用join函數將列表中的子字符串組合成一個字符串。join函數將一個參數放在join前面,表示使用這個字符(或字符串)在子字符串之間進行組合:

print("Output #25: {0}".format(','.join(string2_list)))

在這個示例中,附加參數為一個逗號,位于圓括號中。所以join函數將子字符串組合成一個字符串,子字符串之間為逗號。因為列表中有6個子字符串,所以子字符串被組合成一個字符串,子字符串之間有5個逗號。新生成的字符串是Your,deliverable,is,due,in,June。

3.strip

下面兩組示例展示了如何使用strip、lstrip和rstrip函數從字符串兩端刪除不想要的字符。這3個函數都可以在括號中使用一個附加參數來設定要從字符串兩端刪除的字符(或字符串)。

第一組示例展示了如何使用lstrip、rstrip和strip函數分別從字符串的左側、右側和兩側刪除空格、制表符和換行符:

string3 = " Remove unwanted characters    from this string.\t\t    \n"
print("Output #26: string3: {0:s}".format(string3))
string3_lstrip = string3.lstrip()
print("Output #27: lstrip: {0:s}".format(string3_lstrip))
string3_rstrip = string3.rstrip()
print("Output #28: rstrip: {0:s}".format(string3_rstrip))
string3_strip = string3.strip()
print("Output #29: strip: {0:s}".format(string3_strip))

string3的左側含有幾個空格。此外,右側包含制表符(\t)、幾個空格和換行符(\n)。如果以前你沒有見過\t和\n,那么現在知道了這是計算機中表示制表符和換行符的方法。

在Output #26中,你會看到句子前面有空白字符;在句子下面有一個空行,因為有換行符;在句子后面你看不到制表符和空格,但它們確實在那兒。Output #27、Output #28和Output #29分別展示了從字符串左側、右側和兩側刪除空格、制表符和換行符的方法。{0:s}中的s表示傳入print語句的值應該格式化為一個字符串。

第二組示例展示了從字符串兩端刪除其他字符的方法,將這些字符作為strip函數的附加參數即可。

string4 = "$$Here's another string that has unwanted characters.__---++"
print("Output #30: {0:s}".format(string4))
string4 = "$$The unwanted characters have been removed.__---++"
string4_strip = string4.strip('$_-+')
print("Output #31: {0:s}".format(string4_strip))

在這組示例中,美元符號($)、下劃線(_)、短劃線(-)和加號(+)需要從字符串兩端刪除。通過將這些字符作為附加參數,可以通知程序從字符串兩端刪除它們。在Output #31中,結果字符串為The unwanted characters have been removed.。

4.replace

下面兩個示例展示了如何使用replace函數將字符串中的一個或一組字符替換為另一個或另一組字符。這個函數在括號中使用兩個附加參數,第一個參數是要在字符串中查找替換的字符或一組字符,第二個參數是要用來替換掉第一個參數的字符或一組字符:

string5 = "Let's replace the spaces in this sentence with other characters."
string5_replace = string5.replace(" ", "!@!")
print("Output #32 (with !@!): {0:s}".format(string5_replace))
string5_replace = string5.replace(" ", ",")
print("Output #33 (with commas): {0:s}".format(string5_replace))

Output #32展示了如何使用replace函數將字符串中的空格替換為!@!。結果字符串為Let's!@!replace!@!the!@!spaces !@!in!@!this!@!sentence!@!with!@!other!@!characters.。

Output #33展示了如何使用逗號替換字符串中的空格。結果字符串為Let's,replace,the, spaces,in,this,sentence,with,other,characters.。

5.lower、upper、capitalize

最后3個示例展示了如何使用lower、upper和capitalize函數。lower和upper函數分別用來將字符串中的字母轉換為小寫和大寫。capitalize函數對字符串中的第一個字母應用upper函數,對其余的字母應用lower函數:

string6 = "Here's WHAT Happens WHEN You Use lower."
print("Output #34: {0:s}".format(string6.lower()))
string7 = "Here's what Happens when You Use UPPER."
print("Output #35: {0:s}".format(string7.upper()))
string5 = "here's WHAT Happens WHEN you use Capitalize."
print("Output #36: {0:s}".format(string5.capitalize()))
string5_list = string5.split()
print("Output #37 (on each word):")
for word in string5_list:
    print("{0:s}".format(word.capitalize()))

Output #34和Output #35是對lower和upper函數最直接的應用。在字符串上應用了這些函數之后,string6中的所有字母都是小寫,string7中的所有字母都是大寫。

Output #36和Output #37演示了capitalize函數。Output #36說明了capitalize函數對字符串中的第一個字母應用upper函數,對其余的字母應用lower函數。Output #37將capitalize函數放在一個for循環中。for循環是一個控制流結構,將在后面詳細討論,現在不妨先看一下。

代碼for word in string5_list的意義是這樣的:“對于string5_list這個列表中的每個元素,需要做點事情。”下面的代碼print word.capitalize()說明了對于列表中的每個元素要做的事情。這兩行代碼放在一起的意義就是:“對于string5_list這個列表中的每個元素,先應用capitalize函數,然后再打印出來。”代碼執行的結果就是列表中的每個單詞首字母大寫,其余字母小寫。

Python中還有更多管理字符串的模塊和函數。和內置函數math一樣,你可以參考Python標準庫(https://docs.python.org/3/library/index.html)來獲取更多信息。

1.4.3 正則表達式與模式匹配

很多商業分析都依賴模式匹配,也稱為正則表達式(regular expression)。舉例來說,你可能需要分析一下來自某個州(比如馬里蘭州)的所有訂單。在這種情況下,你需要識別的模式就是Maryland這個單詞。同樣,你還可能需要分析一下來自某個供應商(比如StaplesRUs)的商品質量,那么你要識別的模式就是StaplesRUs。

Python包含了re模塊,它提供了在文本中搜索特定模式(也就是正則表達式)的強大功能。要在腳本中使用re模塊提供的功能,需要在腳本上方加入import re這行代碼,放在上一個import語句之后。現在first_script.py的上方應該是這樣的:

#!/usr/bin/env python3
from math import exp, log, sqrt
import re

通過導入re模塊,你可以使用一大波函數和元字符來創建和搜索任意復雜的模式。元字符(metacharacter)是正則表達式中具有特殊意義的字符。每個元字符都有特殊意義,它們使正則表達式能夠匹配特定的字符串。常用的元字符包括|、()、[]、.、*、+、?、^、$和(?P<name>)。如果你在正則表達式中見到這些字符,要知道程序不是要搜索這些字符本身,而是要搜索它們描述的東西。你可以在Python標準庫中的“Regular Expression Operations”一節中獲得關于元字符的更多信息(https://docs.python.org/3/library/re.html)。

re模塊中還包括很多有用的函數,用于創建和搜索特定的模式(本節要介紹的函數包括re.compile、re.search、re.sub、re.ignorecase和re.I)。來看一下示例代碼:

# 計算字符串中模式出現的次數
string = "The quick brown fox jumps over the lazy dog."
string_list = string.split()
pattern = re.compile(r"The", re.I)
count = 0
for word in string_list:
    if pattern.search(word):
        count += 1
print("Output #38: {0:d}".format(count))

第一行將字符串變量string賦值為The quick brown fox jumps over the lazy dog.。下一行將字符串拆分列表,列表中的每個元素都是一個單詞。

再下一行使用re.compile和re.I函數以及用r表示的原始字符串,創建一個名為pattern的正則表達式。re.compile函數將文本形式的模式編譯成為編譯后的正則表達式。正則表達式不是必須編譯的,但是編譯是個好習慣,因為這樣可以顯著地提高程序運行速度。re.I函數確保模式是不區分大小寫的,能同時在字符串中匹配“The”和“the”。原始字符串標r可以確保Python不處理字符串中的轉義字符,比如\、\t或\n。這樣在進行模式匹配時,字符串中的轉義字符和正則表達式中的元字符就不會有意外的沖突。在上面的例子中,字符串中沒有轉義字符,所以r不是必需的,但是在正則表達式中使用原始字符串標志是一個好習慣。接下來的一行代碼創建了一個變量count來保存字符串中模式出現的次數,初始值設為0。

再下一行是個for循環語句,在列表變量string_list的各個元素之間進行迭代。它取出的第一個元素是“The”這個單詞,取出的第二個元素是“quick”這個單詞,以此類推,直到取出列表中所有的單詞。接下來的一行使用re.search函數將列表中的每個單詞與正則表達式進行比較。如果這個單詞與正則表達式相匹配,函數就返回True,否則就返回None或False。所以if語句的意義就是:如果單詞與正則表達式匹配,那么count的值就加1。

最后,print語句打印出正則表達式在字符串中找到模式“The”(不區分大小寫)的次數,在本例中,找到了兩次。

 太可怕了

正則表達式在進行搜索時的確功能強大,但是非常難以讀懂(它曾被稱為“只用來寫的語言”),所以當你第一次沒有看懂的時候,不用太在意,連專家都不一定能看懂!

當你逐漸熟悉了正則表達式后,使用它們得到你想要的結果就簡單了。要想愉快地學習正則表達式,你可以參考一下Google研發總監Peter Norvig的工作,他創建了一個正則表達式,這個表達式可以匹配美國總統的名字,并且可以篩掉失敗的總統競選人,網址為https://www.oreilly.com/learning/regex-golf-with-peter-norvig

再看另一個示例:

# 在字符串中每次找到模式時將其打印出來
string = "The quick brown fox jumps over the lazy dog."
string_list = string.split()
pattern = re.compile(r"(?P<match_word>The)", re.I)</match_word>
print("Output #39:")
for word in string_list:
    if pattern.search(word):
        print("{:s}".format(pattern.search(word).group('match_word')

第二個示例與第一個示例的區別在于,這個示例是想在屏幕上打印出每個匹配的字符串,而不是匹配的次數。要想得到匹配的字符串,將它們打印到屏幕上或存儲在文件中,需要使用(?P<name>)元字符和group函數。這個示例中的多數代碼和前一個示例中討論過的代碼是一樣的,所以這里重點解釋新的部分。

第一個新代碼片段是(?P<name>),這是一個出現在re.compile函數中的元字符。這個元字符使匹配的字符串可以在后面的程序中通過組名符號<name>來引用。在這個示例中,這個組被稱為<match_word>。

最后一個新代碼片段出現在if語句中。這個代碼片段的意義是:“如果結果為True(也就是說,如果單詞與模式匹配),那么就在search函數返回的數據結構中找出match_word組中的值,并把這些值打印在屏幕上。”

下面是最后一個示例:

# 使用字母“a”替換字符串中的單詞“the”
string = "The quick brown fox jumps over the lazy dog."
string_to_find = r"The"
pattern = re.compile(string_to_find, re.I)
print("Output #40: {:s}".format(pattern.sub("a", string)

最后一個示例展示了如何使用re.sub函數來在文本中用一種模式替換另一種模式。再說一遍,這個示例中的多數代碼和前兩個示例中討論過的代碼是一樣的,所以這里重點解釋新的部分。

第一個新代碼片段將正則表達式賦給變量pattern,于是這個變量可以被傳入到re.compile函數中。解釋一下,在調用re.compile函數之前,將正則表達式賦給變量不是必需的;但是,如果你的正則表達式特別長而且復雜的話,將它賦給一個變量然后將變量傳給re.compile函數這種做法可以使你的代碼更利于理解。

最后一個新代碼片段在最后一行。這個代碼片段使用re.sub函數以不區分大小寫的方式在變量string中尋找模式,然后將發現的每個模式替換成字母a。這次替換的最終結果是a quick brown fox jumps over a lazy dog.。

更多關于正則表達式函數的信息可以在Python標準庫(https://docs.python.org/3/library/index.html)中找到,也可以參考MichaelFitzgerald的著作《學習正則表達式》此書已由人民郵電出版社出版。——編者注

1.4.4 日期

日期在大多數商業應用中都是必不可少的。你需要知道一個事件在何時發生,距離這件事發生還有多少時間,或者幾個事件之間的時間間隔。因為日期是很多應用的核心,也因為日期是一種非常不尋常的數據,在處理時經常要乘以60或24,也有“差不多30分鐘”和“幾乎是365天和一個季度”這樣的說法,所以在Python中對日期有特殊的處理方式。

Python中包含了datetime模塊,它提供了非常強大的功能來處理日期和時間。要想在腳本中使用datetime模塊提供的功能,需要在腳本上方加入from datetime import date, time, datetime, timedelta,放在之前的import語句下面。現在first_script.py的上方應該是這樣的:

#!/usr/bin/env python3
from math import exp, log, sqrt
import re
from datetime import date, time, datetime, timedelta

導入datetime模塊之后,就有各式各樣的日期時間對象和函數供你隨意使用了。常用的對象和函數包括today、year、month、day、timedelta、strftime和strptime。這些函數可以捕獲具體的日期數據(例如:年、月、日)、進行日期和時間的加減運算、創建特定形式的日期字符串以及根據日期字符串創建datetime對象。下面是使用這些datetime對象和函數的幾個示例。第一組示例演示了date對象和datetime對象之間的區別:

# 打印出今天的日期形式,以及年、月、日
today = date.today()
print("Output #41: today: {0!s}".format(today))
print("Output #42: {0!s}".format(today.year))
print("Output #43: {0!s}".format(today.month))
print("Output #44: {0!s}".format(today.day))
current_datetime = datetime.today()
print("Output #45: {0!s}".format(current_datetime))

通過使用date.today(),你可以創建一個date對象,其中包含了年、月、日,但不包含時間元素,比如時、分、秒。相反,通過datetime.today()創建的對象則包含時間元素。{0!s}中的!s表示傳入到print語句中的值應該格式化為字符串,盡管它是個數值型數據。最后,你可以使用year、month和day來捕獲具體的日期元素。

下一個示例演示了如何使用timedelta函數來對date對象進行時間的加減操作:

# 使用timedelta計算一個新日期
one_day = timedelta(days=-1)
yesterday = today + one_day
print("Output #46: yesterday: {0!s}".format(yesterday))
eight_hours = timedelta(hours=-8)
print("Output #47: {0!s} {1!s}".format(eight_hours.days, eight_hours.seconds))

在這個示例中,使用timedelta函數從今天減去了1天。當然,還可以在括號中使用days=10、hours=-8或者weeks=2來創建變量,分別表示未來10天、以前8個小時或者未來2個星期。

在使用timedelta時需要注意的一點是,它將括號中的時間差以天、秒和毫秒的形式存儲,然后將數值規范化后得到一個唯一的值。這說明分鐘、小時和星期會被分別轉換成60秒、3600秒和7天,然后規范化,就是生成天、秒和毫秒“列”(類似于小學數學中的個位、十位等等)。舉例來說,hours=-8的輸出是(-1 days, 57,600 seconds),不是更簡單的(-28,800 seconds)。是這樣計算的:86400秒(3600秒每小時*24小時每天)-28 800秒(3600秒每小時*8小時)= 57 600秒。正如你所見,對負值的規范化乍看上去很令人吃驚,特別是在進行取整和舍入時。

第三個示例展示了如何從一個date對象中減去另一個。相減的結果是個datetime對象,將所得的差以天、小時、分鐘和秒來顯示。例如,在這個示例中結果是“1 day, 0:00:00”:

# 計算出兩個日期之間的天數
date_diff = today - yesterday
print("Output #48: {0!s}".format(date_diff))
print("Output #49: {0!s}".format(str(date_diff).split()[0]))

在某些情況下,你可能只需要結果中的數值部分。舉例來說,在這個示例中你只需要數值1。從結果中得到這個數值的一種方法是使用前面已經討論過的字符串函數。str函數可以將結果轉換成字符串;split函數可以使用空白字符將字符串拆分,并使每個子字符串成為列表的一個元素;[0]表示“取出列表中的第一個元素”,在本例中就是數值1。在1.4.5節中還會看到[0]這種語法,因為它是列表索引,可以用來從列表中取出特定的元素。

第四組示例展示了如何使用strftime函數根據一個date對象來創建具有特定格式的字符串:

# 根據一個日期對象創建具有特定格式的字符串
print("Output #50: {:s}".format(today.strftime('%m/%d/%Y')))
print("Output #51: {:s}".format(today.strftime('%b %d, %Y')))
print("Output #52: {:s}".format(today.strftime('%Y-%m-%d')))
print("Output #53: {:s}".format(today.strftime('%B %d, %Y')))

在我寫這一章的時候,當天的4種打印形式如下:

01/28/2016
Jan 28, 2016
2016-01-28
January 28, 2016

這4個示例說明了如何使用格式符來創建不同格式的日期字符串,格式符包括%Y、%B、%b、%m和%d。你可以在Python標準庫(https://docs.python.org/3/library/datetime.html)的“datetime—Basic date and time types”一節中找到datetime模塊使用的其他格式符。

# 根據一個表示日期的字符串
# 創建一個帶有特殊格式的datetime對象
date1 = today.strftime('%m/%d/%Y')
date2 = today.strftime('%b %d, %Y')
date3 = today.strftime('%Y-%m-%d')
date4 = today.strftime('%B %d, %Y')

# 基于4個具有不同日期格式的字符串
# 創建2個datetime對象和2個date對象
print("Output #54: {!s}".format(datetime.strptime(date1, '%m/%d/%Y')))
print("Output #55: {!s}".format(datetime.strptime(date2, '%b %d, %Y')))
# 僅顯示日期部分
print("Output #56: {!s}".format(datetime.date(datetime.strptime\
(date3, '%Y-%m-%d'))))
print("Output #57: {!s}".format(datetime.date(datetime.strptime\
(date4, '%B %d, %Y'))))

第五組示例展示了如何使用strptime函數根據具有特定形式的日期字符串來創建datetime對象。在這個示例中,date1、date2、date3和date4是字符串變量,以不同的形式表示今天。前兩個print語句展示了將前兩個字符串變量date1和date2轉換成datetime對象的結果。要使它們正確工作,strptime函數中使用的形式需要和傳入函數的字符串變量的形式相匹配。這兩個print語句的輸出結果是個datetime對象,2014-01-28 00:00:00。

有些時候,你可能只對datetime對象中的日期部分感興趣。在這種情況下,你可以使用嵌套的函數(在最后兩個print語句中,是date和strptime)將日期字符串變量轉換為datetime對象,然后僅返回datetime對象的日期部分。這些print語句的結果是2014-01-28。當然,你不需要立刻打印出這個值。你可以將這個日期賦給一個新變量,然后使用這個變量進行計算,洞悉隨著時間產生的業務數據。

1.4.5 列表

很多商業分析中都使用列表。你會維護各種客戶列表、產品列表、資產列表、銷售量列表,等等。但是Python中的列表(對象的可排序集合)更加靈活!上面那些列表中包含的都是相似的對象(例如:包含客戶姓名的字符串或代表銷售量的浮點數),但是Python中的列表可不止這么簡單。它可以包含數值、字符串、其他列表、元組和字典(本章稍后介紹)的任意組合。因為列表在商業應用中使用廣泛、靈活性高、作用突出,所以掌握如何在Python中操作列表是極其重要的。

如你所愿,Python提供了很多有用的函數和操作符來管理列表。以下演示了最常用的和最有效的列表函數和操作符的使用方法。

1.創建列表

# 使用方括號創建一個列表
# 用len()計算列表中元素的數量
# 用max()和min()找出最大值和最小值
# 用count()計算出列表中某個值出現的次數
a_list = [1, 2, 3]
print("Output #58: {}".format(a_list))
print("Output #59: a_list has {} elements.".format(len(a_list)))
print("Output #60: the maximum value in a_list is {}.".format(max(a_list)))
print("Output #61: the minimum value in a_list is {}.".format(min(a_list)))
another_list = ['printer', 5, ['star', 'circle', 9]]
print("Output #62: {}".format(another_list))
print("Output #63: another_list also has {} elements.".format\
(len(another_list)))
print("Output #64: 5 is in another_list {} time.".format(another_list.count(5)))

這個示例展示了如何創建兩個簡單列表,a_list和another_list。將元素放在方括號之間就可以創建列表。a_list包含數值1、2和3。another_list包含一個字符串printer、一個數值5以及一個包含了兩個字符串和一個數值的列表。

這個示例還展示了如何使用4種列表函數:len、min、max和count。len返回列表中元素的個數。min和max分別返回列表中的最小值和最大值。count返回列表中某個元素出現的次數。

2.索引值

# 使用索引值訪問列表中的特定元素
# [0]是第1個元素,[-1]是最后一個元素
print("Output #65: {}".format(a_list[0]))
print("Output #66: {}".format(a_list[1]))
print("Output #67: {}".format(a_list[2]))
print("Output #68: {}".format(a_list[-1]))
print("Output #69: {}".format(a_list[-2]))
print("Output #70: {}".format(a_list[-3]))
print("Output #71: {}".format(another_list[2]))
print("Output #72: {}".format(another_list[-1]))

這個示例說明了如何使用索引值來引用列表中的特定元素。列表索引值從0開始,所以你可以通過在列表名稱后面的方括號中放入0來引用列表中的第一個元素。示例中的第一個print語句print a_list[0]打印出a_list中的第一個元素,即數值1。a_list[1]引用的是列表中的第二個元素,a_list[2]引用的是列表中的第三個元素,以此類推直到列表結束。

這個示例還說明了你可以使用負索引值從列表尾部引用列表元素。列表尾部的索引值從-1開始,所以你可以通過在列表名稱后面的方括號中放入-1來引用列表中的最后一個元素。第四個print語句print a_list[-1]打印出a_list中的最后一個元素,即數值3。a_list[-2]打印出列表中倒數第二個元素,a_list[-3]打印出列表中倒數第三個元素,以此類推直到列表開頭。

3.列表切片

# 使用列表切片訪問列表元素的一個子集
# 從開頭開始切片,可以省略第1個索引值
# 一直切片到末尾,可以省略第2個索引值
print("Output #73: {}".format(a_list[0:2]))
print("Output #74: {}".format(another_list[:2]))
print("Output #75: {}".format(a_list[1:3]))
print("Output #76: {}".format(another_list[1:]

這個示例展示了如何使用列表切片引用列表元素的一個子集。在列表名稱后面的方括號中放入由冒號隔開的兩個索引,就可以創建一個列表切片。列表切片引用的是列表中從第一個索引值到第二個索引值的前一個元素。舉例來說,第一個print語句print a_list[0:2]的意義就是:“打印出a_list中索引值為0和1的元素”。這個print語句打印出[1, 2],因為這是列表中的前兩個元素。

這個示例還說明,如果從列表開頭開始切片,就可以省略第一個索引值,如果一直切片到列表末尾,就可以省略第二個索引值。舉例來說,最后一個print語句print another_list[1:]的意義就是:“從列表中第二個元素開始,打印出another_list中其余所有的元素。”這個print語句打印出[5,['star', 'circle', 9]],因為這是列表中最后兩個元素。

4.列表復制

# 使用[:]復制一個列表
a_new_list = a_list[:]
print("Output #77: {}".format(a_new_list))

這個示例展示了如何復制一個列表。如果你需要對列表進行某種操作,比如添加或刪除元素,或對列表進行排序,但你還希望原始列表保持不變,這時這個功能就非常重要了。要復制一個列表,在列表名稱后面的方括號中放入一個冒號,然后將其賦給一個新的變量即可。在這個示例中,a_new_list是a_list的一個完美復制,所以你可以對a_new_list添加或刪除元素,也可以對a_new_list進行排序,而不會影響a_list。

5.列表連接

# 使用+將兩個或更多個列表連接起來
a_longer_list = a_list + another_list
print("Output #78: {}".format(a_longer_list))

這個示例展示了如何將兩個或更多個列表連接在一起。當你必須分別引用多個具有相似信息的列表,但希望將它們組合起來進行分析的時候,這個功能就非常重要了。舉例來說,由于數據存儲方式的原因,你可能需要生成一個銷售量列表,其中包括來自于一個數據源和另一個數據源的銷售量列表。要將兩個銷售量列表連接在一起進行分析,可以將兩個列表名稱用+操作符相加,然后賦給一個新變量。在這個示例中,a_long_list包含a_list和another_list中的所有元素,將它們連接在一起形成一個更長的列表。

6.使用in和not in

# 使用in和not in來檢查列表中是否有特定元素
a = 2 in a_list
print("Output #79: {}".format(a))
if 2 in a_list:
    print("Output #80: 2 is in {}.".format(a_list))
b = 6 not in a_list
print("Output #81: {}".format(b))
if 6 not in a_list:
    print("Output #82: 6 is not in {}.".format(a_list))

這個示例展示了如何使用in和not in來檢查列表中是否存在某個特定元素。這些表達式的結果是True或False,取決于表達式為真還是假。這個功能在商業應用中非常重要,因為你可以使用它在程序中添加有意義的業務邏輯。舉例來說,它們經常應用于if語句,比如“如果SupplierY在SupplierList中,那么做些什么事,否則做些其他事。”本章后面的內容中將會介紹更多if語句和其他控制流表達式的示例。

7.追加、刪除和彈出元素

# 使用append()向列表末尾追加一個新元素
# 使用remove()從列表中刪除一個特定元素
# 使用pop()從列表末尾刪除一個元素
a_list.append(4)
a_list.append(5)
a_list.append(6)
print("Output #83: {}".format(a_list))
a_list.remove(5)
print("Output #84: {}".format(a_list))
a_list.pop()
a_list.pop()
print("Output #85: {}".format(a_list))

這個示例展示了向列表中添加元素和從列表中刪除元素的方法。append方法將一個新元素追加到列表末尾。你可以使用該方法按照具體業務規則創建列表。舉例來說,要建立一個關于CustomerX的采購量的列表,可以先創建一個名為CustomerX的空列表,然后在記錄所有客戶采購量的主列表中掃描,如果在主列表中發現了CustomerX,就可以將這個采購量數據追加到列表CustomerX中。

remove方法可以刪除列表中的任意元素。你可以使用該方法刪除列表中的錯誤元素和輸入錯誤,還可以按照具體業務規則刪除列表中的元素。在這個示例中,remove方法從a_list中刪除了數值5。

pop方法刪除列表中的最后一個元素。和remove方法相似,你可以使用pop方法刪除列表末尾的錯誤元素和輸入錯誤,還可以按照具體的業務規則從列表末尾刪除元素。在這個示例中,對pop方法的兩次調用從a_list中分別刪除了數值6和數值4。

8.列表反轉

# 使用reverse()原地反轉一個列表會修改原列表
# 要想反轉列表同時又不修改原列表,可以先復制列表
a_list.reverse()
print("Output #86: {}".format(a_list))
a_list.reverse()
print("Output #87: {}".format(a_list))

這個示例展示了使用reverse函數以in-place方式對列表進行反轉的方法(原地反轉)。“in-place”表示反轉操作將原列表修改為順序顛倒的新列表。舉例來說,示例第一次調用reverse函數將a_list改變為[3, 2, 1]。第二次調用reverse函數則將a_list恢復到初始順序。要想使用列表的反轉形式而不修改原列表,可以先復制列表,然后對列表副本進行reverse操作。

9.列表排序

# 使用sort()對列表進行原地排序會修改原列表
# 要想對列表進行排序同時又不修改原列表,可以先復制列表
unordered_list = [3, 5, 1, 7, 2, 8, 4, 9, 0, 6]
print("Output #88: {}".format(unordered_list))
list_copy = unordered_list[:]
list_copy.sort()
print("Output #89: {}".format(list_copy))
print("Output #90: {}".format(unordered_list))

這個示例展示了使用sort函數以in-place方式對列表進行排序的方法。和reverse函數一樣,這種原地排序將原列表修改為排好順序的新列表。要想使用排好順序的列表而不修改原列表,可以先復制列表,然后對列表副本進行sort操作。

10.sorted排序函數

# 使用sorted()對一個列表集合按照列表中某個位置的元素進行排序
my_lists = [[1,2,3,4], [4,3,2,1], [2,4,1,3]]
my_lists_sorted_by_index_3 = sorted(my_lists, key=lambda index_value:\
index_value[3])
print("Output #91: {}".format(my_lists_sorted_by_index_3))

這個示例展示了如何使用sorted函數以及關鍵字函數,對一個列表集合按照每個列表中特定索引位置的值進行排序。關鍵字函數設置用于列表排序的關鍵字。在這個示例中,關鍵字是一個lambda函數,表示使用索引位置為3的值(也就是列表中的第四個元素)對列表進行排序。(后續章節將會對lambda函數做更多討論。)使用每個列表中的第四個元素作為排序關鍵字,應用sorted函數之后,第二個列表[4, 3, 2, 1]成為了第一個列表,第三個列表[2, 4, 1, 3]成為了第二個列表,第一個列表[1, 2, 3, 4]成為了第三個列表。另外,你應該知道sorted函數的排序與sort函數的in-place原地排序方式不同,sort函數改變了原列表的元素順序,sorted函數則返回一個新的排好序的列表,并不改變原列表的元素順序。

下一個排序示例使用operator標準模塊,這個模塊提供的功能可以使用多個關鍵字對列表、元組和字典進行排序。為了在腳本中使用operator模塊中的itemgetter函數,在腳本上方添加from operator import itemgetter:

#!/usr/bin/env python3
from math import exp, log, sqrt
import re
from datetime import date, time, datetime, timedelta
from operator import itemgetter

導入operator模塊中的itemgetter函數后,你可以使用每個列表中多個索引位置的值對列表集合進行排序:

# 使用itemgetter()對一個列表集合按照兩個索引位置來排序
my_lists = [[123,2,2,444], [22,6,6,444], [354,4,4,678], [236,5,5,678], \
[578,1,1,290], [461,1,1,290]]
my_lists_sorted_by_index_3_and_0 = sorted(my_lists, key=itemgetter(3,0))
print("Output #92: {}".format(my_lists_sorted_by_index_3_and_0))

這個示例展示了如何使用sorted()函數和itemgetter函數按照每個列表中多個索引位置的值對列表集合進行排序。關鍵字函數設置用于列表排序的關鍵字。在這個示例中,關鍵字是包含兩個索引值(3和0)的itemgetter函數。這個語句的意義是:“先按照索引位置3中的值對列表進行排序,然后,在這個排序基礎上,按照索引位置0中的值對列表進一步排序。”

這種通過多個元素對列表和其他數據容器進行排序的方法非常重要,因為你經常需要按照多個值對數據進行排序。例如,如果要處理每天銷售交易數據,你需要先按照日期再按照每天的交易量大小進行排序。或者,在處理供應商數據時,你會先按照供應商姓名再按照每個供應商的供貨發票日期來排序。sorted函數和itemgetter函數可以實現這樣的功能。

如果想獲得列表函數的更多信息,可以參考Python標準庫(https://docs.python.org/3/library/index.html)。

1.4.6 元組

元組除了不能被修改之外,其余特點與列表非常相似。正因為元組不能被修改,所以沒有元組修改函數。你可能會感到奇怪,為什么要設計這兩種如此相似的數據結構。這是因為元組具有可修改的列表無法實現的重要作用,例如作為字典鍵值。元組不如列表使用廣泛,所以這里只是簡略地介紹一下。

1.創建元組

# 使用圓括號創建元組
my_tuple = ('x', 'y', 'z')
print("Output #93: {}".format(my_tuple))
print("Output #94: my_tuple has {} elements".format(len(my_tuple)))
print("Output #95: {}".format(my_tuple[1]))
longer_tuple = my_tuple + my_tuple
print("Output #96: {}".format(longer_tuple))

這個示例展示了創建元組的方法。將元素放在括號中間,就可以創建一個元組。此示例還說明,前面討論過的很多應用于列表的函數和操作符,也同樣適用于元組。例如,len函數返回元組中元素的個數,元組索引和元組切片可以引用元組中特定的元素,+操作符可以連接多個元組。

2.元組解包

# 使用賦值操作符左側的變量對元組進行解包
one, two, three = my_tuple
print("Output #97: {0} {1} {2}".format(one, two, three))
var1 = 'red'
var2 = 'robin'
print("Output #98: {} {}".format(var1, var2))
 
# 在變量之間交換彼此的值
var1, var2 = var2, var1
print("Output #99: {} {}".format(var1, var2))

這個示例展示了元組的一個很有意思的操作——解包。可以將元組中的元素解包成為變量,在賦值操作符的左側放上相應的變量就可以了。在這個示例中,字符串x、y和z被解包成為變量one、two和three的值。這個功能可以用來在變量之間交換變量值。在示例的最后一部分,var2的值被賦給var1,var1的值被賦給var2。Python會同時對元組的各個部分求值。這樣,red robin變成了robin red。

3.元組轉換成列表(及列表轉換成元組)

# 將元組轉換成列表,列表轉換成元組
my_list = [1, 2, 3]
my_tuple = ('x', 'y', 'z')
print("Output #100: {}".format(tuple(my_list)))
print("Output #101: {}".format(list(my_tuple)))

最后,可以將元組轉換成列表,也可以將列表轉換成元組。這個功能和可以將一個元素轉換成字符串的str函數很相似。要將一個列表轉換成元組,將列表名稱放在tuple()函數中即可。同樣,要將一個元組轉換成列表,將元組名稱放在list()函數中即可。

如果想獲得元組的更多信息,可以參考Python標準庫(https://docs.python.org/3/library/index.html)。

1.4.7 字典

Python中的字典本質上是包含各種帶有唯一標識符的成對信息的列表。和列表一樣,字典也廣泛應用于各種商業分析。在商業分析中,可以用字典表示客戶(以客戶編碼為鍵值),也可以用字典表示產品(以序列號或產品編號為鍵值),還可以用字典表示資產、銷售量等。

在Python中,這樣的數據結構稱為字典,在其他編程語言中則稱為關聯數組鍵-值存儲散列值。在商業分析中,列表和字典都是非常重要的數據結構,但是它們之間還存在著重要的區別,要想有效地使用字典,必須清楚這些區別。

在列表中,你可以使用被稱為索引索引值的連續整數來引用某個列表值。在字典中,要引用一個字典值,則可以使用整數、字符串或其他Python對象,這些統稱為字典鍵。在唯一鍵值比連續整數更能反映出變量值含義的情況下,這個特點使字典比列表更實用。

在列表中,列表值是隱式排序的,因為索引是連續整數。在字典中,字典值則沒有排序,因為索引不僅僅只是數值。你可以為字典中的項目定義排序操作,但是字典確實沒有內置排序。

在列表中,為一個不存在的位置(索引)賦值是非法的。在字典中,則可以在必要的時候創建新的位置(鍵)。

因為沒有排序,所以當你進行搜索或添加新值時,字典的響應時間更快(當你插入一個新項目時,計算機不需要重新分配索引值)。當處理的數據越來越多時,這是一個重要的考慮因素。

因為字典在商業應用中使用廣泛、靈活性高、作用突出,所以掌握如何在Python中使用字典是極其重要的。下面的示例代碼演示了最常用的和最有效的用于處理字典的函數和操作符的使用方法。

1.創建字典

# 使用花括號創建字典
# 用冒號分隔鍵-值對
# 用len()計算出字典中鍵-值對的數量
empty_dict = { }
a_dict = {'one':1, 'two':2, 'three':3}
print("Output #102: {}".format(a_dict))
print("Output #103: a_dict has {!s} elements".format(len(a_dict)))
another_dict = {'x':'printer', 'y':5, 'z':['star', 'circle', 9]}
print("Output #104: {}".format(another_dict))
print("Output #105: another_dict also has {!s} elements"\
.format(len(another_dict)))

這個示例展示了創建字典的方法。要創建一個空字典,將字典名稱放在等號左側,在等號右側放上一對花括號即可。

示例中的第二個字典a_dict演示了向字典中添加鍵和值的一種方法。a_dict說明鍵和值是用冒號隔開的,花括號之間的鍵-值對則是用逗號隔開的。字典鍵是用單引號或雙引號圍住的字符串,字典值可以是字符串、數值、列表、其他字典或其他Python對象。在a_dict中,字典值是整數,但是在another_dict中,字典值是字符串、數值和列表。最后,這個示例說明了len函數返回的是字典中鍵-值對的個數。

2.引用字典中的值

# 使用鍵來引用字典中特定的值
print("Output #106: {}".format(a_dict['two']))
print("Output #107: {}".format(another_dict['z']

要引用字典中一個特定的值,需要使用字典名稱、一對方括號和一個特定的鍵值(一個字符串)。在這個示例中,a_dict['two']的結果是整數2,another_dict['z']的結果是列表['star', 'circle', 9]。

3.復制

# 使用copy()復制一個字典
a_new_dict = a_dict.copy()
print("Output #108: {}".format(a_new_dict))

要復制一個字典,先在字典名稱后面加上copy函數,然后將這個表達式賦給一個新的字典即可。在這個示例中,a_new_dict是字典a_dict的一個副本。

4.鍵、值和項目

# 使用keys()、values()和items()
# 分別引用字典中的鍵、值和鍵-值對
print("Output #109: {}".format(a_dict.keys()))
a_dict_keys = a_dict.keys()
print("Output #110: {}".format(a_dict_keys))
print("Output #111: {}".format(a_dict.values()))
print("Output #112: {}".format(a_dict.items()))

要引用字典的鍵值,在字典名稱后面加上keys函數即可。這個表達式的結果是包含字典鍵值的一個列表。要引用字典值,在字典名稱后面加上values函數即可。這個表達式的結果是包含字典值的一個列表。

要想同時引用字典的鍵和值,在字典名稱后面加上items函數即可。結果是一個列表,其中包含的是鍵-值對形式的元組。例如,a_dict.items()的結果是[('three', 3), ('two', 2), ('one', 1)]。在1.4.8節中會介紹如何使用for循環來對字典中的所有鍵和值進行解包和引用。

5.使用in、not in和get

if 'y' in another_dict:
    print("Output #114: y is a key in another_dict: {}."\
.format(another_dict.keys()))
if 'c' not in another_dict:
    print("Output #115: c is not a key in another_dict: {}."\
.format(another_dict.keys()))
print("Output #116: {!s}".format(a_dict.get('three')))
print("Output #117: {!s}".format(a_dict.get('four')))
print("Output #118: {!s}".format(a_dict.get('four', 'Not in dict')))

這個示例展示了測試字典中是否存在某個鍵值的兩種方法。第一種方法是使用if語句、in或not in以及字典名稱。使用in、if語句來測試y是否是another_dict中的一個鍵值。如果語句結果為真(也就是說如果y是another_dict中的一個鍵值),那么就執行print語句;否則,就不執行。這種if in和if not in語句經常用于測試是否存在某個鍵值,和其他語句一起使用時,還可以為字典添加新的鍵值。本書后續章節會有添加鍵值的示例。

縮進的作用

你應該注意到if語句后面的行是縮進的。Python使用縮進來表示一個語句是否屬于一個邏輯上的“塊”,也就是說,如果if語句判斷為True,那么if語句后面所有縮進的代碼都要被執行,然后Python解釋器再繼續下一步工作。你會發現這種縮進還會在隨后討論的其他邏輯塊中出現,現在你需要記住的是,在Python中縮進是有明確意義的,你必須遵循這個原則。如果你使用IDE或者像Sublime Text這樣的編輯器,軟件會幫助你設置每次使用制表符都相當于按了一定次數的空格鍵;如果你使用像Notepad一樣的普通文本編輯器,那么就要注意使用同樣數目的空格表示同一級別的縮進(一般使用4個空格)。

最后需要注意的是,有時候文本編輯器會使用制表符代替空格,這樣程序看上去沒有問題,但還是會收到一條錯誤信息(像“Unexpected indent in line 37”),這就會令程序員非常抓狂。盡管有這個問題,一般來說,Python使用縮進還是會使代碼更加清晰易讀,因為你可以輕松地理解程序的邏輯過程。

第二種測試具體鍵值的方式是使用get函數,這個函數也可以按照鍵值取得相應的字典值。和前一種測試鍵值的方式不同,如果字典中存在這個鍵,get函數就返回鍵值對應的字典值;如果字典中不存在這個鍵,則返回None。此外,get函數還可以使用第二個參數,表示如果字典中不存在鍵值時函數的返回值。通過這種方式,如果字典中不存在該鍵值,可以返回除None之外的一些其他內容。

6.排序

# 使用sorted()對字典進行排序
# 要想對字典排序的同時不修改原字典
# 先復制字典
print("Output #119: {}".format(a_dict))
dict_copy = a_dict.copy()
ordered_dict1 = sorted(dict_copy.items(), key=lambda item: item[
print("Output #120 (order by keys): {}".format(ordered_dict1))
ordered_dict2 = sorted(dict_copy.items(), key=lambda item: item[1])
print("Output #121 (order by values): {}".format(ordered_dict2))
ordered_dict3 = sorted(dict_copy.items(), key=lambda x: x[1], reverse=True)
print("Output #122 (order by values, descending): {}".format(ordered_dict3))
ordered_dict4 = sorted(dict_copy.items(), key=lambda x: x[1], reverse=False)
print("Output #123 (order by values, ascending): {}".format(ordered_dict4))

這個示例展示了如何使用不同方式對字典進行排序。本節開頭就說過,字典沒有隱含排序,但是,你可以使用前面的代碼片段對一個字典對象進行排序。可以按照字典的鍵或字典值來排序,如果這些值是數值型的,排序方式可以是升序,也可以是降序。

這個示例中使用了copy函數來為字典a_dict制作一個副本,副本的名稱為dict_copy。為字典制作副本確保了原字典a_dict不會被修改。下一行代碼中包含了sorted函數、一個由items函數生成的元組列表和一個作為sorted函數關鍵字的lambda函數。

這行代碼比較復雜,可以先將它分解一下。這行代碼的目的是對items函數生成的鍵-值元組列表按照某種規則進行排序。這種規則就是key,它相當于一個簡單的lambda函數。(lambda函數是一個簡單函數,在運行時返回一個表達式。)在這個lambda函數中,item是唯一的參數,表示由items函數返回的每個鍵-值元組。冒號后面是要返回的表達式,這個表達式是item[0],即返回元組中的第一個元素(也就是字典鍵值),用作sorted函數的關鍵字。簡而言之,這行代碼的意義是:將字典中的鍵-值對按照字典鍵值升序排序。下一個sorted函數使用item[1]而不是item[0],所以這行代碼按照字典值對鍵-值對進行升序排序。

最后兩行代碼中的sorted函數與它們前面一行代碼中的sorted函數很相似,因為這3個sorted函數都使用字典值作為排序關鍵字。又因為這個字典的值是數值型的,所以可以按照升序或降序對其進行排序。最后兩種排序方式展示了如何使用sorted函數的reverse參數來設定排序結果是升序還是降序。reverse=True對應降序,所以鍵-值對按照字典值以降序排序。

要想獲得更多關于字典的信息,請參考Python標準庫(https://docs.python.org/3/library/index.html)。

1.4.8 控制流

控制流元素非常重要,因為可以在程序中包含有意義的業務邏輯。很多商務處理和分析依賴于業務邏輯,例如“如果客戶的花費超過一個具體值,那么就怎樣怎樣”或“如果銷售額屬于A類,則編碼為X,如果銷售額屬于B類,則編碼為Y,否則編碼為Z。”這些邏輯語句在代碼中可以用控制流元素來表示。

Python提供了若干種控制流元素,包括if-elif-else語句、for循環、range函數和while循環。正如它們的名字所示,if-else語句提供的邏輯為“如果這樣,那么就做那個,否則做些別的事情”。else代碼塊并不是必需的,但可以使你的代碼更加清楚。for循環可以使你在一系列值之間進行迭代,這些值可以是列表、元組或字符串。你可以使用range函數與len函數一起作用于列表,生成一個索引序列,然后用在for循環中。最后,只要while條件為真,while循環會一直執行內部的代碼。

1.if-else

# if-else語句
x = 5
if x > 4 or x != 9:
    print("Output #124: {}".format(x))
else:
    print("Output #124: x is not greater than 4")

第一個示例展示了一個簡單的if-else語句。if條件判斷x是否大于4或者x是否不等于9(!=操作法表示“不等于”)。通過使用or操作符,在找到一個表達式為True時就停止判斷。在這個例子中,x等于5,5大于4,所以x != 9是不用判斷的。第一個條件x > 4為真,所以print x被執行,打印結果為數值5。如果if代碼塊中沒有一個條件為真,那么就執行else代碼塊中的print語句。

2.if-elif-else

# if-elif-else語句
if x > 6:
    print("Output #125: x is greater than six")
elif x > 4 and x == 5:
    print("Output #125: {}".format(x*x))
else:
    print("Output #125: x is not greater than 4")

第二個示例展示了一個稍微復雜一點的if-elif-else語句。同前一個示例一樣,if代碼塊測試x是否大于6。如果這個條件為真,那么停止判斷,執行相應的print語句。實際上,5不大于6,所以使用下面的elif語句繼續判斷。這個語句測試x是否大于4并且x是否等于5。使用and操作符進行的判斷在找到一個表達式為False時就停止。在這個例子中, x等于5,5大于4,并且求的x值是5,所以執行print x*x,打印結果為數值25。因為這里已經使用了等號作為對象賦值操作符,所以用兩個等號(==)判斷是否相等。如果if和elif代碼塊都不為真,那么就執行else代碼塊中的print語句。

3.for循環

y = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', \
'Nov', 'Dec']
z = ['Annie', 'Betty', 'Claire', 'Daphne', 'Ellie', 'Franchesca', 'Greta', \
'Holly', 'Isabel', 'Jenny']
 
print("Output #126:")
for month in y:
     print("{!s}".format(month))
 
print("Output #127: (index value: name in list)")
for i in range(len(z)):
     print("{0!s}: {1:s}".format(i, z[i]))
 
print("Output #128: (access elements in y with z's index values)")
for j in range(len(z)):
     if y[j].startswith('J'):
         print("{!s}".format(y[j]))
 
print("Output #129:")
for key, value in another_dict.items():
     print("{0:s}, {1}".format(key, value))

這4個for循環示例演示了如何使用for循環在序列中迭代。在本書后面的章節,以及一般的商業應用中,這種功能都非常重要。第一個for循環示例展示了基本語法,即for variable in sequence,做某事。variable是一個臨時占位符,表示序列中的各個值,并且只在for循環中有意義。在這個示例中,變量名為month。sequence是你要進行迭代的序列的名稱。同樣在這個示例中,序列名為y,是一個月份列表。因此,這個示例的意義是:“對于y中的每個值,打印出這個值。”

第二個for循環示例展示了如何使用range函數和len函數的組合生成一個可以在for循環中使用的索引值序列。為了弄清楚復合函數之間的相互作用,可以仔細地分析一下。len函數返回列表z中元素的個數,這里的個數是10。然后range函數生成了一系列整數,是從0開始直到比len函數的結果少1的整數,在這個例子中,就是0~9的整數。因此,for循環的意義就是:“對于0~9的整數序列中的一個整數i,打印出整數i,再打印一個空格,然后打印出列表z中索引值為i的元素值。”在本書的很多示例中,你都會看到range函數和len函數的組合用在for循環中,因為這種組合在很多商業應用中是非常有用的。

第三個for循環示例展示了如何使用從一個序列中生成的索引值來引用另一個序列中具有同樣索引值的元素,也說明了如何在for循環中包含if語句來說明業務邏輯。這個示例又一次使用range函數和len函數生成了列表z的索引值。然后,if語句測試列表y中具有這些索引值的元素(y[0]='Jan', y[1]='Feb', …, y[9]='Oct')是否以大寫字母J開頭。

最后一個for循環展示了在字典的鍵和值之間進行迭代和引用的方法。在for循環的第一行中,items函數返回字典的鍵-值元組。for循環中的key和value變量依次捕獲這些值。 for循環中的print語句在每一行打印出一個鍵-值對,鍵和值之間以逗號隔開。

4.簡化for循環:列表、集合與字典生成式

列表、集合與字典生成式是Python中一種簡化的for循環寫法。列表生成式出現在方括號內,集合生成式與字典生成式則出現在花括號內。所有的生成式都包括條件邏輯(例如:if-else語句)。

列表生成式。下面的示例展示了如何使用列表生成式來從一個列表集合中篩選出符合特定條件的列表子集:

# 使用列表生成式選擇特定的行
my_data = [[1,2,3], [4,5,6], [7,8,9]]
rows_to_keep = [row for row in my_data if row[2] > 5]
print("Output #130 (list comprehension): {}".format(rows_to_keep))

在這個示例中,列表生成式的意義是:對于my_data中的每一行,如果這行中索引位置2的值(即第三個值)大于5,則保留這一行。因為6和9都大于5,所以rows_to_keep中的列表子集為[4, 5, 6]和[7, 8, 9]。

集合生成式。下面的示例展示了如何使用集合生成式來從一個元組列表中選擇出特定的元組集合:

# 使用集合生成式在列表中選擇出一組唯一的元組
my_data = [(1,2,3), (4,5,6), (7,8,9), (7,8,9)]
set_of_tuples1 = {x for x in my_data}
print("Output #131 (set comprehension): {}".format(set_of_tuples1))
set_of_tuples2 = set(my_data)
print("Output #132 (set function): {}".format(set_of_tuples2))

在這個示例中,集合生成式的意義是:對于my_data中的每個元組,如果它是一個唯一的元組,則保留這個元組。你可以稱這個表達式為集合生成式而不是列表生成式,因為表達式中是花括號,不是方括號,而且它也不是字典生成式,因為它沒有使用鍵-值對這樣的語法。

這個示例中的第二個print語句說明你可以通過Python內置的set函數達到和集合生成式同樣的效果。在這個例子中,使用內置的set函數更好,因為它比集合生成式更精煉,而且更易讀。

字典生成式。下面的示例展示了如何使用字典生成式來從一個字典中篩選出滿足特定條件的鍵-值對子集:

# 使用字典生成式選擇特定的鍵-值對
my_dictionary = {'customer1': 7, 'customer2': 9, 'customer3': 11}
my_results = {key : value for key, value in my_dictionary.items() if \
value > 10}
print("Output #133 (dictionary comprehension): {}".format(my_results))

在這個例子中,字典生成式的意義為:對于my_dictionay中的每個鍵-值對,如果值大于10,則保留這個鍵-值對。因為值11大于10,所以保留在my_results中的鍵-值對為{'customer3':11}。

5.while循環

print("Output #134:")
x = 0
while x < 11:
    print("{!s}".format(x))
    x += 1

這個示例展示了如何使用while循環來打印0~10的整數。x = 0將變量x初始化為0。然后while循環判斷x是否小于11。因為x小于11,所以while循環在一行中打印出x值,然后將x的值增加1。接著while循環繼續判斷x(此時為1)是否小于11。因為確實小于11,所以繼續執行while循環內部的語句。這個過程一直以這種方式繼續,直到x從10增加到11。這時,while循環繼續判斷x是否小于11,表達式結果為假,while循環內部語句不再被執行。

while循環適合于知道內部語句會被執行多少次的情況。更多時候,你不太確定內部語句需要執行多少次,這時就應該使用for循環。

6.函數

在一些情況下,你會發現自己編寫函數比使用Python內置函數和安裝別人開發的模塊更方便有效。舉例來說,如果你發現總是在不斷重復地書寫同樣的代碼片段,那么就應該考慮將這個代碼片段轉換為函數。某些情況下,函數可能已經存在于Python基礎模塊或“可導入”的模塊中了。如果函數已經存在,就應該使用這些開發好并已經通過了大量測試的函數。但是,有些情況下,你需要的函數不存在或不可用,這時就需要你自己創建函數。

要在Python中創建函數,需要使用def關鍵字,并在后面加上函數名稱和一對圓括號,然后再加上一個冒號。組成函數主體的代碼需要縮進。最后,如果函數需要返回一個或多個值,可以使用return關鍵字來返回函數結果供程序使用。下面的示例展示了在Python中創建和使用函數的方法:

# 計算一系列數值的均值
def getMean(numericValues):
    return sum(numericValues)/len(numericValues) if len(numericValues) > 0
    else float('nan')
 
my_list = [2, 2, 4, 4, 6, 6, 8, 8]
print("Output #135 (mean): {!s}".format(getMean(my_list))

這個示例展示了如何創建函數來計算一系列數值的均值。函數名為getMean。圓括號之間的短語表示要傳入函數中的數值序列,這是一個僅在函數作用范圍內有意義的變量。在函數內部,由序列的總和除以序列中數值的個數計算出序列的均值。此外,還可以使用if-else語句來檢驗序列中是否包含數值。如果確實包含數值,函數返回序列均值。如果不包含數值,函數返回nan(即:非數值)。如果省略了if-else語句,而且序列中正好沒有任何數值的話,程序就會拋出一個除數為0的錯誤。最后,使用return關鍵字返回函數結果供程序使用。

在這個示例中,my_list包含了8個數值。my_list被傳入getMean()函數中。8個數值的總和為40,40除以8等于5。所以,print語句打印出整數5。

就像你猜測的那樣,mean函數已經存在了。例如,NumPy中就有一個mean函數。所以,你可以通過導入NumPy,使用它里面的mean函數得到同樣的結果:

import numpy as np
print np.mean(my_list)

再說一次,如果在Python基礎程序或可導入模塊中,已經存在你需要的函數,就應該使用這些開發好的并已經通過了大量測試的函數。使用Google或Bing來搜索“<你需要的功能描述> Python function”可以幫助你找到想要的Python函數。但是,如果你想完成業務過程中特有的任務,知道如何去創建函數還是非常重要的。

7.異常

編寫一個強壯穩健的程序的一個重要方面就是有效地處理錯誤和異常。在編寫程序時,你可能會隱含地假設程序要處理的數據類型和數據結構,如果有數據違反了你的假設,就會使程序拋出錯誤。

Python中包含了若干種內置的異常對象。常用的異常包括IOError、IndexError、KeyError、NameError、SyntaxError、TypeError、UnicodeError和ValueError。你可以在網上獲得更多的異常信息,參見Python標準庫中的“Built-in Exceptions”那一節(http://docs.python.org/3/library/exceptions.html)。你可以使用try-except來構筑處理錯誤信息的第一道防線,即使數據不匹配,你的程序還可以繼續運行。

下面展示了兩種使用try-except代碼塊來有效地捕獲和處理異常的方法(一種比較短,另一種比較長)。這兩個示例修改了上一節的函數示例,來說明如何使用try-except代碼塊代替if語句處理空列表的情況。

8.try-except

# 計算一系列數值的均值
def getMean(numericValues):
    return sum(numericValues)/len(numericValues)
my_list2 = [ ]
 
# 簡單形式
try:
    print("Output #138: {}".format(getMean(my_list2)))
except ZeroDivisionError as detail:
    print("Output #138 (Error): {}".format(float('nan')))
    print("Output #138 (Error): {}".format(detail))

在這種處理異常的方法中,函數getMean()中沒有檢驗序列是否包含數值的if語句。如果序列是空的,就像列表my_list2一樣,那么調用這個函數會導致一個異常ZeroDivisionError。

要想使用try-except代碼塊,需要將你要執行的代碼放在try代碼塊中,然后,使用except代碼塊來處理潛在的錯誤并打印出錯誤信息來幫助你理解程序錯誤。在某些情況下,異常具有特定的值。你可以通過在except行中加上as短語來引用異常值,然后打印出你為異常指定的變量。因為my_list2中不包含任何數值,所以執行except代碼塊,打印出nan和Error: float division by zero。

9.try-except-else-finally

# 完整形式
try:
    result = getMean(my_list2)
except ZeroDivisionError as detail:
    print "Output #142 (Error): " + str(float('nan'))
    print "Output #142 (Error):", detail
else:
    print "Output #142 (The mean is):", result
finally:
    print "Output #142 (Finally): The finally block is executed every time"

這個完整形式的異常處理方法除了try和except代碼塊,還包含else和finally代碼塊。如果try代碼塊成功執行,則會接著執行else代碼塊。因此,如果傳遞給try代碼塊中的getMean()函數的數值序列中包含任意數值,那么這些數值的均值就會被賦給try代碼塊中的變量result,然后接著執行else代碼塊。例如,如果這里使用程序代碼+my_list1+,就會打印出The mean is: 5.0。因為my_list2不包含任何數值,所以執行except代碼塊,打印出nan和Error: float division by zero。然后,總是執行finally模塊,打印出The finally block is executed every time。

主站蜘蛛池模板: 岳西县| 禄劝| 南和县| 日土县| 乌拉特后旗| 毕节市| 广河县| 元氏县| 阿城市| 鹤山市| 台南县| 青海省| 溆浦县| 盘锦市| 香港 | 莱芜市| 兴海县| 阳曲县| 新宾| 西盟| 溧水县| 绥宁县| 彰化市| 缙云县| 西乌| 南通市| 禹州市| 大竹县| 杭州市| 巫山县| 淮安市| 博爱县| 福泉市| 永春县| 海丰县| 科技| 闸北区| 湖北省| 九龙城区| 伊金霍洛旗| 阿拉善右旗|