△命令行模式和Python交互模式
在Windows開始菜單選擇“命令提示符”,就進入到命令行模式,它的提示符類似C:\>;
在命令行模式下敲命令python,就看到類似如下的一堆文本輸出,然后就進入到Python交互模式,它的提示符是>>>。
在Python交互模式下輸入exit()並回車,就退出了Python交互模式,並回到命令行模式。
△Python文件名只能是英文字母、數字和下划線的組合。(但是實踐證明中文也是可以的,但是不建議。)
print()會依次打印每個字符串,遇到逗號“,”會輸出一個空格
△每一行都是一個語句,當語句以冒號:結尾時,縮進的語句視為代碼塊。
縮進有利有弊。好處是強迫你寫出格式化的代碼,但沒有規定縮進是幾個空格還是Tab。按照約定俗成的管理,應該始終堅持使用4個空格的縮進。
縮進的另一個好處是強迫你寫出縮進較少的代碼,你會傾向於把一段很長的代碼拆分成若干函數,從而得到縮進較少的代碼。
縮進的壞處就是“復制-粘貼”功能失效了,這是最坑爹的地方。當你重構代碼時,粘貼過去的代碼必須重新檢查縮進是否正確。此外,IDE很難像格式化Java代碼那樣格式化Python代碼。
最后,請務必注意,Python程序是大小寫敏感的,如果寫錯了大小寫,程序會報錯。
△整數和浮點數在計算機內部存儲的方式是不同的,整數運算永遠是精確的(除法難道也是精確的?是的!無論整數做//(地板除)除法還是取余數%,結果永遠是整數,所以,整數運算結果永遠是精確的。而/除法計算結果是浮點數,即使是兩個整數恰好整除,結果也是浮點數),而浮點數運算則可能會有四舍五入的誤差。
注意:Python的整數沒有大小限制;
Python的浮點數也沒有大小限制,但是超出一定范圍就直接表示為inf(無限大)。
△字符串是以單引號'或雙引號"括起來的任意文本,請注意,''或""本身只是一種表示方式,不是字符串的一部分。如果'本身也是一個字符,那就可以用""括起來。如果字符串內部既包含'又包含"可以用轉義字符\來標識(比如:'I\'m\"OK\"!'),也可以用三個單引號或者三個雙引號來標識(比如:'''I'm "OK"!''')。
Python還允許用r''表示''內部的字符串默認不轉義(比如:>>> print(r'\\\t\\') 打印結果為 \\\t\\)。
如果字符串內部有很多換行,用\n寫在一行里不好閱讀,為了簡化,Python允許用'''多行內容'''的格式表示多行內容。(比如:
a='''line1
line2
line3'''
print(a)
打印結果為
line1
line2
line3)
△空值是Python里一個特殊的值,用None表示。None不能理解為0,因為0是有意義的,而None是一個特殊的空值。
△所謂常量就是不能變的變量,比如常用的數學常數π就是一個常量。在Python中,通常用全部大寫的變量名表示常量:PI = 3.14159265359
但事實上PI仍然是一個變量,Python根本沒有任何機制保證PI不會被改變,所以,用全部大寫的變量名表示常量只是一個習慣上的用法,如果你一定要改變變量PI的值,也沒人能攔住你。
△字符編碼
ASCII編碼(1個字節) → Unicode編碼(2個字節) → UTF-8編碼(可變字節)
現在計算機系統通用的字符編碼工作方式:
在計算機內存中,統一使用Unicode編碼,當需要保存到硬盤或者需要傳輸的時候,就轉換為UTF-8編碼。(內存中是Unicode,輸出設備上就是UTF-8)
△Python的字符串
對於單個字符的編碼,Python提供了ord()函數獲取字符的整數表示,chr()函數把編碼轉換為對應的字符:
>>> ord('A')
65
>>> chr(66)
'B'
由於Python的字符串類型是str,在內存中以Unicode表示,一個字符對應若干個字節。如果要在網絡上傳輸,或者保存到磁盤上,就需要把str變為以字節為單位的bytes。
Python對bytes類型的數據用帶b前綴的單引號或雙引號表示:
x = b'ABC'
要注意區分'ABC'和b'ABC',前者是str,后者雖然內容顯示得和前者一樣,但bytes的每個字符都只占用一個字節。
以Unicode表示的str通過encode()方法可以編碼為指定的bytes,例如:
>>> 'ABC'.encode('ascii')
b'ABC'
>>> '中文'.encode('utf-8')
b'\xe4\xb8\xad\xe6\x96\x87'
反過來,如果我們從網絡或磁盤上讀取了字節流,那么讀到的數據就是bytes。要把bytes變為str,就需要用decode()方法:
>>> b'ABC'.decode('ascii')
'ABC'
>>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
'中文'
len()函數計算的是str的字符數,如果換成bytes,len()函數就計算字節數:
>>> len('中文')
2
>>> len('中文'.encode('utf-8'))
6
△格式化
%運算符就是用來格式化字符串的。在字符串內部,%s表示用字符串替換,%d表示用整數替換,有幾個%?占位符,后面就跟幾個變量或者值,順序要對應好。如果只有一個%?,括號可以省略。
占位符 替換內容
%d 整數
%f 浮點數
%s 字符串
%x 十六進制整數
其中,格式化整數和浮點數還可以指定是否補0和整數與小數的位數:
print('%2d-%02d' % (3, 1)) 結果為 3-01
print('%.2f' % 3.1415926) 結果為 3.14
如果你不太確定應該用什么,%s永遠起作用,它會把任何數據類型轉換為字符串。
另一種格式化字符串的方法是使用字符串的format()方法,它會用傳入的參數依次替換字符串內的占位符{0}、{1}……,不過這種方式寫起來比%要麻煩得多:
>>> 'Hello, {0}, 成績提升了 {1:.1f}%'.format('小明', 17.125)
'Hello, 小明, 成績提升了 17.1%'
△Python內置的一種數據類型是列表:list。list是一種有序且可變的集合(可以看成是一個數組),可以隨時添加和刪除其中的元素。list里面的元素的數據類型也可以不同。用len()函數可以獲得list元素的個數;用索引來訪問list中每一個位置的元素,記得從左邊起索引是從0開始的,從右邊起索引是從-1開始的。
>>> classmates = ['Michael', 'Bob', 'Tracy']
>>> classmates
['Michael', 'Bob', 'Tracy']
1追加元素到末尾:>>> classmates.append('Adam')
>>> classmates
['Michael', 'Bob', 'Tracy', 'Adam']
2把元素插入到指定的位置:>>> classmates.insert(1, 'Jack')
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy', 'Adam']
3要刪除list末尾的元素,用pop()方法:>>> classmates.pop()
'Adam'
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy']
4要刪除指定位置的元素,用pop(i)方法,其中i是索引位置:>>> classmates.pop(1)
'Jack'
>>> classmates
['Michael', 'Bob', 'Tracy']
5要把某個元素替換成別的元素,可以直接賦值給對應的索引位置:>>> classmates[1] = 'Sarah'
>>> classmates
['Michael', 'Sarah', 'Tracy']
注意:這幾個對list的操作中,只有刪除(4和5)是本身有輸出的。
另一種有序且指向不變(tuple所謂的“不變”是說,tuple的每個元素,指向永遠不變。理解了“指向不變”后,要創建一個內容也不變的tuple怎么做?那就必須保證tuple的每一個元素本身也不能變)的列表叫元組:tuple。tuple和list非常類似,但是tuple一旦初始化就不能修改,當你定義一個tuple時,在定義的時候,tuple的元素就必須被確定下來:>>> classmates = ('Michael', 'Bob', 'Tracy')
只有1個元素的tuple定義時必須加一個逗號,,來消除歧義(>>> t = (1)這種情況下,Python規定,按數學公式中的小括號進行計算,計算結果是1):>>> t = (1,)
>>> t
(1,)
小結:list和tuple是Python內置的有序集合,一個可變,一個不可變。
△條件判斷 if語句(注意不要少寫了冒號:。)
if <條件判斷1>:
<執行1>
elif <條件判斷2>:
<執行2>
elif <條件判斷3>:
<執行3>
else:
<執行4>
if語句執行有個特點,它是從上往下判斷,如果在某個判斷上是True,把該判斷對應的語句執行后,就忽略掉剩下的elif和else。
△循環(for和while條件語句后面也有冒號:的 )
一種是for...in循環,依次把list或tuple中的每個元素迭代出來
names = ['Michael', 'Bob', 'Tracy']
for name in names:
print(name)
執行這段代碼,會依次打印names的每一個元素
Python提供一個range()函數,可以生成一個整數序列,再通過list()函數可以轉換為list。比如range(5)生成的序列是從0開始小於5的整數:
>>> list(range(5))
[0, 1, 2, 3, 4]
第二種循環是while循環,只要條件滿足,就不斷循環,條件不滿足時退出循環。在循環中,break語句可以提前退出循環;在循環過程中,也可以通過continue語句,跳過當前的這次循環,直接開始下一次循環。
小結:
break語句可以在循環過程中直接退出循環,而continue語句可以提前結束本輪循環,並直接開始下一輪循環。這兩個語句通常都必須配合if語句使用。
要特別注意,不要濫用break和continue語句。break和continue會造成代碼執行邏輯分叉過多,容易出錯。大多數循環並不需要用到break和continue語句,常常可以通過改寫循環條件或者修改循環邏輯,去掉break和continue語句。
程序陷入“死循環”,也就是永遠循環下去。這時可以用Ctrl+C退出程序。
△Python內置了字典:dict(key-value存儲方式)的支持,dict全稱dictionary,在其他語言中也稱為map,使用鍵-值(key-value)存儲,具有極快的查找速度。>>> d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
把數據放入dict的方法,除了初始化時指定外,還可以通過key放入(多次對一個key放入value,后面的值會把前面的值沖掉):>>> d['Adam'] = 67
>>> d['Adam']
67
要避免key不存在的錯誤,有兩種辦法,一是通過in判斷key是否存在:>>> 'Thomas' in d
False
二是通過dict提供的get()方法,如果key不存在,可以返回None,或者自己指定的value:
>>> d.get('Thomas') #注意:返回None的時候Python的交互環境不顯示結果。
>>> d.get('Thomas', -1)
-1
要刪除一個key,用pop(key)方法,對應的value也會從dict中刪除:>>> d.pop('Bob')
75
>>> d
{'Michael': 95, 'Tracy': 85}
請務必注意,dict內部存放的順序和key放入的順序是沒有關系的。dict是用空間來換取時間的一種方法。
dict的key必須是不可變對象。在Python中,字符串、整數等都是不可變的,因此,可以放心地作為key。而list是可變的,就不能作為key。
set和dict類似,也是一組key的集合,但不存儲value。由於key不能重復,所以,在set中,沒有重復的key。
要創建一個set,需要提供一個list作為輸入集合:
>>> s = set([1, 2, 3])
>>> s
{1, 2, 3}
注意,傳入的參數[1, 2, 3]是一個list,而顯示的{1, 2, 3}只是告訴你這個set內部有1,2,3這3個元素,顯示的順序也不表示set是有序的。
重復元素在set中自動被過濾:
>>> s = set([1, 1, 2, 2, 3, 3])
>>> s
{1, 2, 3}
通過add(key)方法可以添加元素到set中,可以重復添加,但不會有效果。
通過remove(key)方法可以刪除元素。
set可以看成數學意義上的無序和無重復元素的集合,因此,兩個set可以做數學意義上的交集、並集等操作:
>>> s1 = set([1, 2, 3])
>>> s2 = set([2, 3, 4])
>>> s1 & s2
{2, 3}
>>> s1 | s2
{1, 2, 3, 4}
注意:
set和dict的唯一區別僅在於沒有存儲對應的value,但是,set的原理和dict一樣,所以,同樣不可以放入可變對象,因為無法判斷兩個可變對象是否相等,也就無法保證set內部“不會有重復元素”。
>>> classmates = ['Michael', 'Bob', 'Tracy'] #list 用[]定義
>>> classmates = ('Michael', 'Bob', 'Tracy') #tuple 用()定義
>>> d = {'Michael': 95, 'Bob': 75, 'Tracy': 85} #dic 用{}來定義
>>> s = set([1, 2, 3]) #set 用()來定義
△函數
數據類型轉換
>>> int(12.34)
12
函數名其實就是指向一個函數對象的引用,完全可以把函數名賦給一個變量,相當於給這個函數起了一個“別名”:
>>> a = abs # 變量a指向abs函數
>>> a(-1) # 所以也可以通過a調用abs函數
1
△定義函數
在Python中,定義一個函數要使用def語句,依次寫出函數名、括號、括號中的參數和冒號:,然后,在縮進塊中編寫函數體,函數的返回值用return語句返回。
我們以自定義一個求絕對值的my_abs函數為例:
def my_abs(x):
if not isinstance(x, (int, float)):
raise TypeError('bad operand type')
if x >= 0:
return x
else:
return -x
請注意,函數體內部的語句在執行時,一旦執行到return時,函數就執行完畢,並將結果返回。因此,函數內部通過條件判斷和循環可以實現非常復雜的邏輯。
如果沒有return語句,函數執行完畢后也會返回結果,只是結果為None。return None可以簡寫為return。
空函數
如果想定義一個什么事也不做的空函數,可以用pass語句:
def nop():
pass
pass語句什么都不做,那有什么用?實際上pass可以用來作為占位符,比如現在還沒想好怎么寫函數的代碼,就可以先放一個pass,讓代碼能運行起來。
小結
定義函數時,需要確定函數名和參數個數;
如果有必要,可以先對參數的數據類型做檢查;
函數體內部可以用return隨時返回函數結果;
函數執行完畢也沒有return語句時,自動return None。
函數可以同時返回多個值,但其實就是一個tuple。
練習
請定義一個函數quadratic(a, b, c),接收3個參數,返回一元二次方程:
ax2 + bx + c = 0
的兩個解。
提示:計算平方根可以調用math.sqrt()函數:
import math
參考代碼如下:
1 def quadratic(a, b, c): 2 if not isinstance(a,(int,float)) | isinstance(b,(int,float)) | isinstance(a,(int,float)): 3 raise TypeError('bad operand type') 4 if b**2-4*a*c>=0 : 5 x1=(-b+math.sqrt(b**2-4*a*c))/(2*a) 6 x2=(-b-math.sqrt(b**2-4*a*c))/(2*a) 7 return(x1,x2) 8 else : 9 return('wrong ') 10 # 測試: 11 print('quadratic(2, 3, 1) =', quadratic(2, 3, 1)) 12 print('quadratic(1, 3, -4) =', quadratic(1, 3, -4)) 13 14 if quadratic(2, 3, 1) != (-0.5, -1.0): 15 print('測試失敗') 16 elif quadratic(1, 3, -4) != (1.0, -4.0): 17 print('測試失敗') 18 else: 19 print('測試成功') 20 21 運行結果: 22 quadratic(2, 3, 1) = (-0.5, -1.0) 23 quadratic(1, 3, -4) = (1.0, -4.0) 24 測試成功
△函數的參數
位置參數
power(x, n),用來計算xn,
1 def power(x, n): 2 s = 1 3 while n > 0: 4 n = n - 1 5 s = s * x 6 return s 7 8 #測試代碼: 9 >>> power(5, 2) 10 25 11 >>> power(5, 3) 12 125
power(x, n)函數有兩個參數:x和n,這兩個參數都是位置參數,調用函數時,傳入的兩個值按照位置順序依次賦給參數x和n。
默認參數
由於我們經常計算x2,所以,完全可以把第二個參數n的默認值設定為2:
1 def power(x, n=2): 2 s = 1 3 while n > 0: 4 n = n - 1 5 s = s * x 6 return s 7 8 #測試代碼: 9 >>> power(5) 10 25 11 >>> power(5, 2) 12 25
這樣,當我們調用power(5)時,相當於調用power(5, 2);而對於n > 2的其他情況,就必須明確地傳入n,比如power(5, 3)。
從上面的例子可以看出,默認參數可以簡化函數的調用。設置默認參數時,有幾點要注意:
一是必選參數在前,默認參數在后,否則Python的解釋器會報錯;
二是如何設置默認參數。
當函數有多個參數時,把變化大的參數放前面,變化小的參數放后面。變化小的參數就可以作為默認參數。
使用默認參數有什么好處?最大的好處是能降低調用函數的難度,而一旦需要更復雜的調用時,又可以傳遞更多的參數來實現。無論是簡單調用還是復雜調用,函數只需要定義一個。
也可以不按順序提供部分默認參數。當不按順序提供部分默認參數時,需要把參數名寫上。
注意:
可變參數
在Python函數中,還可以定義可變參數。顧名思義,可變參數就是傳入的參數個數是可變的,可以是1個、2個到任意個,還可以是0個。
我們以數學題為例子,給定一組數字a,b,c……,請計算a2 + b2 + c2 + ……。
1 def calc(*numbers): 2 sum = 0 3 for n in numbers: 4 sum = sum + n * n 5 return sum 6 7 #測試 8 #調用該函數時,可以傳入任意個參數,包括0個參數 9 >>> calc(1, 2) 10 5 11 >>> calc() 12 0 13 14 #Python允許你在list或tuple前面加一個*號,把list或tuple的元素變成可變參數傳進去; *nums表示把nums這個list的所有元素作為可變參數傳進去。這種寫法相當有用,而且很常見 15 >>> nums = [1, 2, 3] 16 >>> calc(*nums) 17 14
成員運算符: not in 、in (判斷某個單詞里是不是有某個字母)
成員運算符用來判斷一個元素是否是另一個元素的成員。 比如說我們可以判斷 “hello” 中是否有 “h”, 得到的結果也是True 或者 False。
1 >>> "h" in "hello" # 這里的意思是 “h” 在“Hello” 中,判斷后結果為True 2 True 3 >>> "h" not in "hello" # 這里的意思是 “h” 不在“Hello” 中,判斷后結果為False 4 False
身份運算符: is、is not(講數據類型時講解,一般用來判斷變量的數據類型)
用來判斷身份。
1 >>> a = 123456 2 >>> b = a 3 >>> b is a #判斷 a 和 b 是不是同一個 123456 4 True 5 >>> c = 123456 6 >>> c is a #判斷 c 和 a 是不是同一個 123456 7 False 8 >>> c is not a #判斷 c 和 a 是不是不是同一個 123456 9 True
這里我們首先將123456賦值給a,后有將a賦值給b, 這樣其實是 a和b 的值都是123456, 但是后面c的值也是123456,為什么 第一次a is b 的結果為True ,c和 a 的結果為False 呢?
原因是這樣的: 我們知道程序是運行在內存里的,第一次 我們將123456賦值給a的時候,其實是在內存里開辟了一塊空間,將123456放在這塊空間里,為了找到這里的123456, 會有一個指向這塊空間的地址,這個地址叫做內存地址,是123456存儲在內存中的地址。a其實指向的就是存儲123456的內存空間的地址。執行了b=a,就是讓b指向的地址和a一樣。之后我們執行了 c = 123456 ,這里就會再開辟一塊內存空間,並將指向該空間的內存地址賦值給c ,這樣的話 ,a和b 指向的是同一個123456, c 指向的是另外一個123456 。

關鍵字參數
可變參數允許你傳入0個或任意個參數,這些可變參數在函數調用時自動組裝為一個tuple。而關鍵字參數允許你傳入0個或任意個含參數名的參數,這些關鍵字參數在函數內部自動組裝為一個dict。
1 def person(name, age, **kw): 2 print('name:', name, 'age:', age, 'other:', kw) 3 4 #調用 5 >>> extra = {'city': 'Beijing', 'job': 'Engineer'} 6 >>> person('Jack', 24, **extra) 7 name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'} 8 9 #**extra表示把extra這個dict的所有key-value用關鍵字參數傳入到函數的**kw參數,kw將獲得一個dict,注意kw獲得的dict是extra的一份拷貝,對kw的改動不會影響到函數外的extra。
關鍵字參數有什么用?它可以擴展函數的功能。比如,在person函數里,我們保證能接收到name和age這兩個參數,但是,如果調用者願意提供更多的參數,我們也能收到。試想你正在做一個用戶注冊的功能,除了用戶名和年齡是必填項外,其他都是可選項,利用關鍵字參數來定義這個函數就能滿足注冊的需求。
命名關鍵字參數
對於關鍵字參數,函數的調用者可以傳入任意不受限制的關鍵字參數。至於到底傳入了哪些,就需要在函數內部通過kw檢查。如果要限制關鍵字參數的名字,就可以用命名關鍵字參數,例如,仍以person()函數為例,只接收city和job作為關鍵字參數。這種方式定義的函數如下:
1 #和關鍵字參數**kw不同,命名關鍵字參數需要一個特殊分隔符*,*后面的參數被視為命名關鍵字參數。 2 def person(name, age, *, city, job): 3 print(name, age, city, job) 4 5 #調用方式如下: 6 >>> person('Jack', 24, city='Beijing', job='Engineer') 7 Jack 24 Beijing Engineer 8 9 #如果函數定義中已經有了一個可變參數,后面跟着的命名關鍵字參數就不再需要一個特殊分隔符*了: 10 def person(name, age, *args, city, job): 11 print(name, age, args, city, job) 12 13 #命名關鍵字參數必須傳入參數名,這和位置參數不同。如果沒有傳入參數名,調用將報錯: 14 >>> person('Jack', 24, 'Beijing', 'Engineer') 15 Traceback (most recent call last): 16 File "<stdin>", line 1, in <module> 17 TypeError: person() takes 2 positional arguments but 4 were given 18 #上面的錯誤說的是:由於調用時缺少參數名city和job,Python解釋器把這4個參數均視為位置參數,但person()函數僅接受2個位置參數。 19 20 21 #命名關鍵字參數可以有缺省值,從而簡化調用: 22 def person(name, age, *, city='Beijing', job): 23 print(name, age, city, job) 24 25 #由於命名關鍵字參數city具有默認值,調用時,可不傳入city參數: 26 >>> person('Jack', 24, job='Engineer') 27 Jack 24 Beijing Engineer
使用命名關鍵字參數時,要特別注意,如果沒有可變參數,就必須加一個*作為特殊分隔符。如果缺少*,Python解釋器將無法識別位置參數和命名關鍵字參數:
def person(name, age, city, job):
# 缺少 *,city和job被視為位置參數
pass
參數組合(不是很理解!!!)
在Python中定義函數,可以用必選參數、默認參數、可變參數、關鍵字參數和命名關鍵字參數,這5種參數都可以組合使用。但是請注意,參數定義的順序必須是:必選參數、默認參數、可變參數、命名關鍵字參數和關鍵字參數。
對於任意函數,都可以通過類似func(*args, **kw)的形式調用它,無論它的參數是如何定義的。
1 #必選參數:應該就是位置參數 2 #默認參數: 3 def power(x, n=2): pass #把位置參數的某一個設置為默認固定的 4 #可變參數: 5 def calc(*numbers): pass #可變參數允許你傳入0個或任意個參數,這些可變參數在函數調用時自動組裝為一個tuple(或list)。 6 #命名關鍵字參數: 7 def person(name, age, *, city, job):pass #*后面的參數被視為命名關鍵字參數(即只接收city和job作為關鍵字參數)。 8 #關鍵字參數: 9 def person(name, age, **kw):pass #關鍵字參數允許你傳入0個或任意個含參數名的參數,這些關鍵字參數在函數內部自動組裝為一個dict。
小結
Python的函數具有非常靈活的參數形態,既可以實現簡單的調用,又可以傳入非常復雜的參數。
默認參數一定要用不可變對象,如果是可變對象,程序運行時會有邏輯錯誤!
要注意定義可變參數和關鍵字參數的語法:
*args是可變參數,args接收的是一個tuple;
**kw是關鍵字參數,kw接收的是一個dict。
以及調用函數時如何傳入可變參數和關鍵字參數的語法:
可變參數既可以直接傳入:func(1, 2, 3),又可以先組裝list或tuple,再通過*args傳入:func(*(1, 2, 3));
關鍵字參數既可以直接傳入:func(a=1, b=2),又可以先組裝dict,再通過**kw傳入:func(**{'a': 1, 'b': 2})。
使用*args和**kw是Python的習慣寫法,當然也可以用其他參數名,但最好使用習慣用法。
命名的關鍵字參數是為了限制調用者可以傳入的參數名,同時可以提供默認值。
定義命名的關鍵字參數在沒有可變參數的情況下不要忘了寫分隔符*,否則定義的將是位置參數。
練習
以下函數允許計算兩個數的乘積,請稍加改造,變成可接收一個或多個數並計算乘積:
1 # -*- coding: utf-8 -*- 2 def product(*numbers): 3 if len(numbers)==0: 4 raise TypeError('bad operand type') 5 mul=1 6 for n in numbers: 7 mul=mul*n 8 return mul 9 # 測試 10 print('product(5) =', product(5)) 11 print('product(5, 6) =', product(5, 6)) 12 print('product(5, 6, 7) =', product(5, 6, 7)) 13 print('product(5, 6, 7, 9) =', product(5, 6, 7, 9)) 14 if product(5) != 5: 15 print('測試失敗!') 16 elif product(5, 6) != 30: 17 print('測試失敗!') 18 elif product(5, 6, 7) != 210: 19 print('測試失敗!') 20 elif product(5, 6, 7, 9) != 1890: 21 print('測試失敗!') 22 else: 23 try:#下面兩行代碼就意味着這個自定義的函數的參數個數不能為0。 24 product() 25 print('測試失敗!') 26 except TypeError: 27 print('測試成功!')
△遞歸函數
如果一個函數在內部調用自身本身,這個函數就是遞歸函數。
在計算機中,函數調用是通過棧(stack)這種數據結構實現的,每當進入一個函數調用,棧就會加一層棧幀,每當函數返回,棧就會減一層棧幀。由於棧的大小不是無限的,所以,遞歸調用的次數過多,會導致棧溢出。解決遞歸調用棧溢出的方法是通過尾遞歸優化,事實上尾遞歸和循環的效果是一樣的,所以,把循環看成是一種特殊的尾遞歸函數也是可以的。
尾遞歸是指,在函數返回的時候,調用自身本身,並且,return語句不能包含表達式。這樣,編譯器或者解釋器就可以把尾遞歸做優化,使遞歸本身無論調用多少次,都只占用一個棧幀,不會出現棧溢出的情況。
尾遞歸調用時,如果做了優化,棧不會增長,因此,無論多少次調用也不會導致棧溢出。
遺憾的是,大多數編程語言沒有針對尾遞歸做優化,Python解釋器也沒有做優化,所以,即使把上面的fact(n)函數改成尾遞歸方式,也會導致棧溢出。
練習
漢諾塔的移動可以用遞歸函數非常簡單地實現。
請編寫move(n, a, b, c)函數,它接收參數n,表示3個柱子A、B、C中第1個柱子A的盤子數量,然后打印出把所有盤子從A借助B移動到C的方法,例如:
1 # -*- coding: utf-8 -*- 2 def move(n, a, b, c): 3 if n == 1: 4 print(a, '-->', c) #a上只有1個盤子 5 else:#a上有n個盤子 6 move(n-1,a,c,b) #把a上的n-1塊移動到b 7 move(1,a,b,c) #把a上的最后一塊移動到c 8 move(n-1,b,a,c) #把b上的n-1塊移動到c 9 10 11 # 期待輸出: 12 # A --> C 13 # A --> B 14 # C --> B 15 # A --> C 16 # B --> A 17 # B --> C 18 # A --> C 19 move(3, 'A', 'B', 'C')
高級特性
在Python中,代碼不是越多越好,而是越少越好。代碼不是越復雜越好,而是越簡單越好。請始終牢記,代碼越少,開發效率越高。
△切片
取一個list或tuple或字符串的指定索引范圍的部分元素,Python提供了切片(Slice)操作符。
L[0:3]表示,從索引0開始取,直到索引3為止,但不包括索引3,如果第一個索引是0,還可以省略;記住倒數第一個元素的索引是-1。
△迭代
如果給定一個list或tuple,我們可以通過for循環來遍歷這個list或tuple,這種遍歷我們稱為迭代(Iteration)。
在Python中,迭代是通過for ... in來完成的。只要作用於一個可迭代對象,for循環就可以正常運行。
如何判斷一個對象是可迭代對象呢?方法是通過collections模塊的Iterable類型判斷:
1 >>> from collections import Iterable 2 >>> isinstance('abc', Iterable) # str是否可迭代 3 True 4 >>> isinstance([1,2,3], Iterable) # list是否可迭代 5 True 6 >>> isinstance(123, Iterable) # 整數是否可迭代 7 False
Python內置的enumerate函數可以把一個list變成索引-元素對:
1 >>> for i, value in enumerate(['A', 'B', 'C']): 2 ... print(i, value) 3 ... 4 0 A 5 1 B 6 2 C
△列表生成式
列表生成式即List Comprehensions,是Python內置的非常簡單卻強大的可以用來創建list的生成式。
寫列表生成式時,把要生成的元素x * x放到前面,后面跟for循環,for循環后面還可以加上if判斷:
>>> [x * x for x in range(1, 11) if x % 2 == 0] [4, 16, 36, 64, 100]
△生成器
在Python中,這種列表元素可以按照某種算法推算出來,一邊循環一邊計算的機制,稱為生成器:generator。
要創建一個generator,有很多種方法。
第一種方法很簡單,只要把一個列表生成式的[]改成(),就創建了一個generator:
1 >>> L = [x * x for x in range(10)] #列表生成式 2 >>> L 3 [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 4 >>> g = (x * x for x in range(10)) #生成器 5 >>> g 6 <generator object <genexpr> at 0x1022ef630>
打印出generator的每一個元素,正確的方法是使用for循環,因為generator也是可迭代對象:
1 >>> g = (x * x for x in range(10)) 2 >>> for n in g: 3 ... print(n) 4 ... 5 0 6 1 7 4 8 9 9 16 10 25 11 36 12 49 13 64 14 81
定義generator的另一種方法。如果一個函數定義中包含yield關鍵字,那么這個函數就不再是一個普通函數,而是一個generator。
generator和函數的執行流程不一樣。函數是順序執行,遇到return語句或者最后一行函數語句就返回。而變成generator的函數,在每次調用next()的時候執行,遇到yield語句返回,再次執行時從上次返回的yield語句處繼續執行。但是用for循環調用generator時,發現拿不到generator的return語句的返回值。如果想要拿到返回值,必須捕獲StopIteration錯誤,返回值包含在StopIteration的value中:
小結
generator是非常強大的工具,在Python中,可以簡單地把列表生成式改成generator,也可以通過函數實現復雜邏輯的generator。
要理解generator的工作原理,它是在for循環的過程中不斷計算出下一個元素,並在適當的條件結束for循環。對於函數改成的generator來說,遇到return語句或者執行到函數體最后一行語句,就是結束generator的指令,for循環隨之結束。
請注意區分普通函數和generator函數,普通函數調用直接返回結果,generator函數的“調用”實際返回一個generator對象。
△迭代器
可以直接作用於for循環的數據類型有以下幾種:
一類是集合數據類型,如list、tuple、dict、set、str等;
一類是generator,包括生成器和帶yield的generator function。
這些可以直接作用於for循環的對象統稱為可迭代對象:Iterable。
可以使用isinstance()判斷一個對象是否是Iterable對象。
而生成器不但可以作用於for循環,還可以被next()函數不斷調用並返回下一個值,直到最后拋出StopIteration錯誤表示無法繼續返回下一個值了。
可以被next()函數調用並不斷返回下一個值的對象稱為迭代器:Iterator。
可以使用isinstance()判斷一個對象是否是Iterator對象。
生成器都是Iterator對象,但list、dict、str雖然是Iterable,卻不是Iterator。
把list、dict、str等Iterable變成Iterator可以使用iter()函數:
1 >>> isinstance(iter([]), Iterator) 2 True 3 >>> isinstance(iter('abc'), Iterator) 4 True
小結
凡是可作用於for循環的對象都是Iterable類型;
凡是可作用於next()函數的對象都是Iterator類型,它們表示一個惰性計算的序列;
集合數據類型如list、dict、str等是Iterable但不是Iterator,不過可以通過iter()函數獲得一個Iterator對象。
Python的for循環本質上就是通過不斷調用next()函數實現的。
■函數式編程
△高階函數:
變量可以指向函數:函數本身也可以賦值給變量,即:變量可以指向函數。例如
>>> f = abs >>> f <built-in function abs>
>>> f(-10) 10
函數名也是變量:函數名其實就是指向函數的變量!
對於abs()這個函數,完全可以把函數名abs看成變量,它指向一個可以計算絕對值的函數!如果把abs指向其他對象,會有什么情況發生?
>>> abs = 10 >>> abs(-10) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'int' object is not callable
把abs指向10后,就無法通過abs(-10)調用該函數了!因為abs這個變量已經不指向求絕對值函數而是指向一個整數10!
當然實際代碼絕對不能這么寫,這里是為了說明函數名也是變量。要恢復abs函數,請重啟Python交互環境。
傳入函數
既然變量可以指向函數,函數的參數能接收變量,那么一個函數就可以接收另一個函數作為參數,這種函數就稱之為高階函數。編寫高階函數,就是讓函數的參數能夠接收別的函數。
小結
把函數作為參數傳入,這樣的函數稱為高階函數,函數式編程就是指這種高度抽象的編程范式。
△△ map/reduce
map()函數接收兩個參數,一個是函數,一個是Iterable,map將傳入的函數依次作用到序列的每個元素,並 把結果作為新的Iterator返回。
reduce把一個函數作用在一個序列[x1, x2, x3, ...]上,這個函數必須接收兩個參數,reduce把結果繼續和序列的下一個元素做累積計算,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
△△ filter
filter()也接收一個函數和一個序列。和map()不同的是,filter()把傳入的函數依次作用於每個元素,然后 根據返回值是True還是False決定保留還是丟棄該元素。
可見用filter()這個高階函數,關鍵在於正確實現一個“篩選”函數。
注意到filter()函數返回的是一個Iterator,也就是一個惰性序列,所以要強迫filter()完成計算結果,需要 用list()函數獲得所有結果並返回list。
小結
filter()的作用是從一個序列中篩出符合條件的元素。由於filter()使用了惰性計算,所以只有在取 filter()結果的時候,才會真正篩選並每次返回下一個篩出的元素。
△△ sorted
Python內置的sorted()函數就可以對list進行排序:
>>> sorted([36, 5, -12, 9, -21]) [-21, -12, 5, 9, 36]
此外,sorted()函數也是一個高階函數,它還可以接收一個key函數來實現自定義的排序,例如按絕對值大小 排序:
>>> sorted([36, 5, -12, 9, -21], key=abs) [5, 9, -12, -21, 36]
△返回函數
△ 匿名函數
△裝飾器
△偏函數
