廖雪峰Python筆記


△命令行模式和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 測試成功
View Code

 

 

△函數的參數

位置參數

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)函數有兩個參數:xn,這兩個參數都是位置參數,調用函數時,傳入的兩個值按照位置順序依次賦給參數xn

默認參數

由於我們經常計算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的解釋器會報錯;
二是如何設置默認參數。

當函數有多個參數時,把變化大的參數放前面,變化小的參數放后面。變化小的參數就可以作為默認參數。

使用默認參數有什么好處?最大的好處是能降低調用函數的難度,而一旦需要更復雜的調用時,又可以傳遞更多的參數來實現。無論是簡單調用還是復雜調用,函數只需要定義一個。

也可以不按順序提供部分默認參數。當不按順序提供部分默認參數時,需要把參數名寫上。

注意: 定義默認參數要牢記一點:默認參數必須指向不變對象!

為什么要設計strNone這樣的不變對象呢?因為不變對象一旦創建,對象內部的數據就不能修改,這樣就減少了由於修改數據導致的錯誤。此外,由於對象不變,多任務環境下同時讀取對象不需要加鎖,同時讀一點問題都沒有。我們在編寫程序時,如果可以設計一個不變對象,那就盡量設計成不變對象。

 

可變參數

在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函數里,我們保證能接收到nameage這兩個參數,但是,如果調用者願意提供更多的參數,我們也能收到。試想你正在做一個用戶注冊的功能,除了用戶名和年齡是必填項外,其他都是可選項,利用關鍵字參數來定義這個函數就能滿足注冊的需求。

 

命名關鍵字參數

 對於關鍵字參數,函數的調用者可以傳入任意不受限制的關鍵字參數。至於到底傳入了哪些,就需要在函數內部通過kw檢查。如果要限制關鍵字參數的名字,就可以用命名關鍵字參數,例如,仍以person()函數為例,只接收cityjob作為關鍵字參數。這種方式定義的函數如下:

 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('測試成功!')
View Code

 

△遞歸函數

如果一個函數在內部調用自身本身,這個函數就是遞歸函數。

在計算機中,函數調用是通過棧(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')
View Code

 


 高級特性

在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錯誤,返回值包含在StopIterationvalue中:

小結

generator是非常強大的工具,在Python中,可以簡單地把列表生成式改成generator,也可以通過函數實現復雜邏輯的generator。

要理解generator的工作原理,它是在for循環的過程中不斷計算出下一個元素,並在適當的條件結束for循環。對於函數改成的generator來說,遇到return語句或者執行到函數體最后一行語句,就是結束generator的指令,for循環隨之結束。

請注意區分普通函數和generator函數,普通函數調用直接返回結果,generator函數的“調用”實際返回一個generator對象。

 

 

△迭代器

可以直接作用於for循環的數據類型有以下幾種:

一類是集合數據類型,如listtupledictsetstr等;

一類是generator,包括生成器和帶yield的generator function。

這些可以直接作用於for循環的對象統稱為可迭代對象:Iterable

可以使用isinstance()判斷一個對象是否是Iterable對象。

 

而生成器不但可以作用於for循環,還可以被next()函數不斷調用並返回下一個值,直到最后拋出StopIteration錯誤表示無法繼續返回下一個值了。

可以被next()函數調用並不斷返回下一個值的對象稱為迭代器:Iterator

可以使用isinstance()判斷一個對象是否是Iterator對象。

 

生成器都是Iterator對象,但listdictstr雖然是Iterable,卻不是Iterator

listdictstrIterable變成Iterator可以使用iter()函數:

1 >>> isinstance(iter([]), Iterator)
2 True
3 >>> isinstance(iter('abc'), Iterator)
4 True

小結

凡是可作用於for循環的對象都是Iterable類型;

凡是可作用於next()函數的對象都是Iterator類型,它們表示一個惰性計算的序列;

集合數據類型如listdictstr等是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()函數接收兩個參數,一個是函數,一個是Iterablemap將傳入的函數依次作用到序列的每個元素,並         把結果作為新的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]

  

△返回函數

△ 匿名函數

△裝飾器

△偏函數

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM