計(jì)算機(jī)顧名思義就是可以做數(shù)學(xué)計(jì)算的機(jī)器,因此,計(jì)算機(jī)程序理所當(dāng)然地可以處理各種數(shù)值。但是,計(jì)算機(jī)能處理的遠(yuǎn)不止數(shù)值,還可以處理文本、圖形、音頻、視頻、網(wǎng)頁等各種各樣的數(shù)據(jù),不同的數(shù)據(jù),需要定義不同的數(shù)據(jù)類型。在Python中,能夠直接處理的數(shù)據(jù)類型有以下幾種:
一、整數(shù)
Python可以處理任意大小的整數(shù),當(dāng)然包括負(fù)整數(shù),在Python程序中,整數(shù)的表示方法和數(shù)學(xué)上的寫法一模一樣,例如:1,100,-8080,0,等等。
計(jì)算機(jī)由于使用二進(jìn)制,所以,有時(shí)候用十六進(jìn)制表示整數(shù)比較方便,十六進(jìn)制用0x前綴和0-9,a-f表示,例如:0xff00,0xa5b4c3d2,等等。
二、浮點(diǎn)數(shù)
浮點(diǎn)數(shù)也就是小數(shù),之所以稱為浮點(diǎn)數(shù),是因?yàn)榘凑湛茖W(xué)記數(shù)法表示時(shí),一個(gè)浮點(diǎn)數(shù)的小數(shù)點(diǎn)位置是可變的,比如,1.23x10^9和12.3x10^8是相等的。浮點(diǎn)數(shù)可以用數(shù)學(xué)寫法,如1.23,3.14,-9.01,等等。但是對(duì)于很大或很小的浮點(diǎn)數(shù),就必須用科學(xué)計(jì)數(shù)法表示,把10用e替代,1.23x10^9就是1.23e9,或者12.3e8,0.000012可以寫成1.2e-5,等等。整數(shù)和浮點(diǎn)數(shù)在計(jì)算機(jī)內(nèi)部存儲(chǔ)的方式是不同的,整數(shù)運(yùn)算永遠(yuǎn)是精確的(除法難道也是精確的?是的?。?,而浮點(diǎn)數(shù)運(yùn)算則可能會(huì)有四舍五入的誤差。
三、字符串
字符串是以''或''括起來的任意文本,比如'abc','xyz'等等。請(qǐng)注意,''或''本身只是一種表示方式,不是字符串的一部分,因此,字符串'abc'只有a,b,c這3個(gè)字符。
注:在PHP中也存在對(duì)字符串可以用’’或者””括起來,但是在雙引號(hào)中的變量會(huì)打印出來,而單引號(hào)中的變量則變成一個(gè)普通的字符串。例如:
$foo = 2;
echo 'foo is $foo'; // 打印結(jié)果: foo is 2 echo 'foo is $foo'; // 打印結(jié)果: foo is $foo四、布爾值
布爾值和布爾代數(shù)的表示完全一致,一個(gè)布爾值只有True、False兩種值,要么是True,要么是False,在Python中,可以直接用True、False表示布爾值(請(qǐng)注意大小寫),也可以通過布爾運(yùn)算計(jì)算出來。
布爾值可以用and、or和not運(yùn)算。
and運(yùn)算是與運(yùn)算,只有所有都為 True,and運(yùn)算結(jié)果才是 True。
or運(yùn)算是或運(yùn)算,只要其中有一個(gè)為 True,or 運(yùn)算結(jié)果就是 True。
not運(yùn)算是非運(yùn)算,它是一個(gè)單目運(yùn)算符,把 True 變成 False,F(xiàn)alse 變成 True。
注:C++中也有布爾值,而在C中則沒有這一變量
五、空值
空值是Python里一個(gè)特殊的值,用None表示。None不能理解為0,因?yàn)?是有意義的,而None是一個(gè)特殊的空值。
此外,Python還提供了列表、字典等多種數(shù)據(jù)類型,還允許創(chuàng)建自定義數(shù)據(jù)類型,我們后面會(huì)繼續(xù)講到
print語句print語句可以向屏幕上輸出指定的文字。比如輸出'hello, world',用代碼實(shí)現(xiàn)如下:
>>> print 'hello, world'
注意:
1.當(dāng)我們?cè)赑ython交互式環(huán)境下編寫代碼時(shí),是Python解釋器的提示符,不是代碼的一部分。
2.當(dāng)我們?cè)谖谋揪庉嬈髦芯帉懘a時(shí),千萬不要自己添加 >>>。
print語句也可以跟上多個(gè)字符串,用逗號(hào)“,”隔開,就可以連成一串輸出:
>>> print 'The quick brown fox', 'jumps over', 'the lazy dog'
The quick brown fox jumps over the lazy dog
print會(huì)依次打印每個(gè)字符串,遇到逗號(hào)“,”會(huì)輸出一個(gè)空格,因此,輸出的字符串是這樣拼起來的:
print也可以打印整數(shù),或者計(jì)算結(jié)果:
>>> print 300
300 #運(yùn)行結(jié)果
>>> print 100 + 200
300 #運(yùn)行結(jié)果
因此,我們可以把計(jì)算100 + 200的結(jié)果打印得更漂亮一點(diǎn):
>>> print '100 + 200 =', 100 + 200
100 + 200 = 300 #運(yùn)行結(jié)果
注意: 對(duì)于100 + 200,Python解釋器自動(dòng)計(jì)算出結(jié)果300,但是,'100 + 200 ='是字符串而非數(shù)學(xué)公式,Python把它視為字符串,請(qǐng)自行解釋上述打印結(jié)果。
注釋任何時(shí)候,我們都可以給程序加上注釋。注釋是用來說明代碼的,給自己或別人看,而程序運(yùn)行的時(shí)候,Python解釋器會(huì)直接忽略掉注釋,所以,有沒有注釋不影響程序的執(zhí)行結(jié)果,但是影響到別人能不能看懂你的代碼。給自己的代碼寫上注釋,這是一個(gè)非常好的工作習(xí)慣。
在HTML中
在PHP中有多種注釋符號(hào):
1.使用#,#這是注釋
2.使用/* */,/*這是注釋*/
3.使用//,//這是注釋
后兩種與C語言中的注釋符號(hào)相同
Python的注釋以 # 開頭,后面的文字直到行尾都算注釋
# 這一行全部都是注釋...
print 'hello' # 這也是注釋
注釋還有一個(gè)巧妙的用途,就是一些代碼我們不想運(yùn)行,但又不想刪除,就可以用注釋暫時(shí)屏蔽掉:
# 暫時(shí)不想運(yùn)行下面一行代碼:
# print 'hello, python.'
什么是變量在Python中,變量的概念基本上和初中代數(shù)的方程變量是一致的。
例如,對(duì)于方程式 y=x*x ,x就是變量。當(dāng)x=2時(shí),計(jì)算結(jié)果是4,當(dāng)x=5時(shí),計(jì)算結(jié)果是25。
只是在計(jì)算機(jī)程序中,變量不僅可以是數(shù)字,還可以是任意數(shù)據(jù)類型。
在Python程序中,變量是用一個(gè)變量名表示,變量名必須是大小寫英文、數(shù)字和下劃線(_)的組合,且不能用數(shù)字開頭,比如:
a = 1
變量a是一個(gè)整數(shù)。
t_007 = 'T007'
變量t_007是一個(gè)字符串。
在Python中,等號(hào)=是賦值語句,可以把任意數(shù)據(jù)類型賦值給變量,同一個(gè)變量可以反復(fù)賦值,而且可以是不同類型的變量,例如:
a = 123 # a是整數(shù)
print a
a = 'xuan' # a變?yōu)樽址?/span>
print a
這種變量本身類型不固定的語言稱之為動(dòng)態(tài)語言,與之對(duì)應(yīng)的是靜態(tài)語言。
靜態(tài)語言在定義變量時(shí)必須指定變量類型,如果賦值的時(shí)候類型不匹配,就會(huì)報(bào)錯(cuò)。例如Java是靜態(tài)語言,賦值語句如下(// 表示注釋):
int a = 123; // a是整數(shù)類型變量
a = 'xuan'; // 錯(cuò)誤:不能把字符串賦給整型變量
和靜態(tài)語言相比,動(dòng)態(tài)語言更靈活,就是這個(gè)原因。
請(qǐng)不要把賦值語句的等號(hào)等同于數(shù)學(xué)的等號(hào)。比如下面的代碼:
x = 10
x = x + 2
如果從數(shù)學(xué)上理解x = x + 2那無論如何是不成立的,在程序中,賦值語句先計(jì)算右側(cè)的表達(dá)式x + 2,得到結(jié)果12,再賦給變量x。由于x之前的值是10,重新賦值后,x的值變成12。
最后,理解變量在計(jì)算機(jī)內(nèi)存中的表示也非常重要。當(dāng)我們寫:a = 'ABC'時(shí),Python解釋器干了兩件事情:
1. 在內(nèi)存中創(chuàng)建了一個(gè)'ABC'的字符串;
2. 在內(nèi)存中創(chuàng)建了一個(gè)名為a的變量,并把它指向'ABC'。
也可以把一個(gè)變量a賦值給另一個(gè)變量b,這個(gè)操作實(shí)際上是把變量b指向變量a所指向的數(shù)據(jù),例如下面的代碼:
a = 'ABC'
b = a
a = 'XYZ'
print b
最后一行打印出變量b的內(nèi)容到底是'ABC'呢還是'XYZ'?如果從數(shù)學(xué)意義上理解,就會(huì)錯(cuò)誤地得出b和a相同,也應(yīng)該是'XYZ',但實(shí)際上b的值是'ABC',讓我們一行一行地執(zhí)行代碼,就可以看到到底發(fā)生了什么事:
執(zhí)行a = 'ABC',解釋器創(chuàng)建了字符串 'ABC'和變量 a,并把a(bǔ)指向 'ABC':
執(zhí)行b = a,解釋器創(chuàng)建了變量 b,并把b指向 a 指向的字符串'ABC':
執(zhí)行a = 'XYZ',解釋器創(chuàng)建了字符串'XYZ',并把a(bǔ)的指向改為'XYZ',但b并沒有更改:
所以,最后打印變量b的結(jié)果自然是'ABC'了。
定義字符串前面我們講解了什么是字符串。字符串可以用''或者''括起來表示。
如果字符串本身包含'怎么辦?比如我們要表示字符串 I'm OK ,這時(shí),可以用' '括起來表示:
'I'm OK'
類似的,如果字符串包含',我們就可以用' '括起來表示:
'Learn 'Python' in mooc'
如果字符串既包含'又包含'怎么辦?
這個(gè)時(shí)候,就需要對(duì)字符串的某些特殊字符進(jìn)行“轉(zhuǎn)義”,Python字符串用\進(jìn)行轉(zhuǎn)義。
要表示字符串 Bob said 'I'm OK'.
由于 ' 和 ' 會(huì)引起歧義,因此,我們?cè)谒懊娌迦胍粋€(gè)\表示這是一個(gè)普通字符,不代表字符串的起始,因此,這個(gè)字符串又可以表示為'Bob said \'I\'m OK\'.'
注意:轉(zhuǎn)義字符 \ 不計(jì)入字符串的內(nèi)容中。轉(zhuǎn)義字符在其他腳本語言中也有此用法
常用的轉(zhuǎn)義字符還有:
\n 表示換行
\t 表示一個(gè)制表符
\\ 表示 \ 字符本身
raw字符串與多行字符串
如果一個(gè)字符串包含很多需要轉(zhuǎn)義的字符,對(duì)每一個(gè)字符都進(jìn)行轉(zhuǎn)義會(huì)很麻煩。為了避免這種情況,我們可以在字符串前面加個(gè)前綴r ,表示這是一個(gè) raw 字符串,里面的字符就不需要轉(zhuǎn)義了。例如:
r'\(~_~)/ \(~_~)/'
但是r'...'表示法不能表示多行字符串,也不能表示包含'和 '的字符串
如果要表示多行字符串,可以用'''...'''表示:
'''Line 1
Line 2
Line 3'''
上面這個(gè)字符串的表示方法和下面的是完全一樣的:
'Line 1\nLine 2\nLine 3'
還可以在多行字符串前面添加 r ,把這個(gè)多行字符串也變成一個(gè)raw字符串:
r'''Python is created by 'Guido'.
It is free and easy to learn.
Let's start learn Python in imooc!'''
Unicode字符串字符串還有一個(gè)編碼問題。
因?yàn)橛?jì)算機(jī)只能處理數(shù)字,如果要處理文本,就必須先把文本轉(zhuǎn)換為數(shù)字才能處理。最早的計(jì)算機(jī)在設(shè)計(jì)時(shí)采用8個(gè)比特(bit)作為一個(gè)字節(jié)(byte),所以,一個(gè)字節(jié)能表示的最大的整數(shù)就是255(二進(jìn)制11111111=十進(jìn)制255),0 - 255被用來表示大小寫英文字母、數(shù)字和一些符號(hào),這個(gè)編碼表被稱為ASCII編碼,比如大寫字母 A 的編碼是65,小寫字母 z 的編碼是122。
如果要表示中文,顯然一個(gè)字節(jié)是不夠的,至少需要兩個(gè)字節(jié),而且還不能和ASCII編碼沖突,所以,中國制定了GB2312編碼,用來把中文編進(jìn)去。
類似的,日文和韓文等其他語言也有這個(gè)問題。為了統(tǒng)一所有文字的編碼,Unicode應(yīng)運(yùn)而生。Unicode把所有語言都統(tǒng)一到一套編碼里,這樣就不會(huì)再有亂碼問題了。
Unicode通常用兩個(gè)字節(jié)表示一個(gè)字符,原有的英文編碼從單字節(jié)變成雙字節(jié),只需要把高字節(jié)全部填為0就可以。
因?yàn)镻ython的誕生比Unicode標(biāo)準(zhǔn)發(fā)布的時(shí)間還要早,所以最早的Python只支持ASCII編碼,普通的字符串'ABC'在Python內(nèi)部都是ASCII編碼的。
Python在后來添加了對(duì)Unicode的支持,以Unicode表示的字符串用u'...'表示,比如:
print u'中文'
中文
注意: 不加 u ,中文就不能正常顯示。
Unicode字符串除了多了一個(gè) u 之外,與普通字符串沒啥區(qū)別,轉(zhuǎn)義字符和多行表示法仍然有效:
轉(zhuǎn)義:
u'中文\n日文\n韓文'
多行:
u'''第一行
第二行'''
raw+多行:
ur'''Python的Unicode字符串支持'中文',
'日文',
'韓文'等多種語言'''
如果中文字符串在Python環(huán)境下遇到 UnicodeDecodeError,這是因?yàn)?py文件保存的格式有問題。可以在第一行添加注釋
# -*- coding: utf-8 -*-
目的是告訴Python解釋器,用UTF-8編碼讀取源代碼。然后用Notepad++ 另存為... 并選擇UTF-8格式保存。
整數(shù)和浮點(diǎn)數(shù)Python支持對(duì)整數(shù)和浮點(diǎn)數(shù)直接進(jìn)行四則混合運(yùn)算,運(yùn)算規(guī)則和數(shù)學(xué)上的四則運(yùn)算規(guī)則完全一致。
基本的運(yùn)算:
1 + 2 + 3 # ==> 6
4 * 5 - 6 # ==> 14
7.5 / 8 + 2.1 # ==> 3.0375
使用括號(hào)可以提升優(yōu)先級(jí),這和數(shù)學(xué)運(yùn)算完全一致,注意只能使用小括號(hào),但是括號(hào)可以嵌套很多層:
(1 + 2) * 3 # ==> 9
(2.2 + 3.3) / (1.5 * (9 - 0.3)) # ==> 0.42145593869731807
和數(shù)學(xué)運(yùn)算不同的地方是,Python的整數(shù)運(yùn)算結(jié)果仍然是整數(shù),浮點(diǎn)數(shù)運(yùn)算結(jié)果仍然是浮點(diǎn)數(shù):
1 + 2 # ==> 整數(shù) 3
1.0 + 2.0 # ==> 浮點(diǎn)數(shù) 3.0
但是整數(shù)和浮點(diǎn)數(shù)混合運(yùn)算的結(jié)果就變成浮點(diǎn)數(shù)了:
1 + 2.0 # ==> 浮點(diǎn)數(shù) 3.0
為什么要區(qū)分整數(shù)運(yùn)算和浮點(diǎn)數(shù)運(yùn)算呢?這是因?yàn)檎麛?shù)運(yùn)算的結(jié)果永遠(yuǎn)是精確的,而浮點(diǎn)數(shù)運(yùn)算的結(jié)果不一定精確,因?yàn)橛?jì)算機(jī)內(nèi)存再大,也無法精確表示出無限循環(huán)小數(shù),比如 0.1 換成二進(jìn)制表示就是無限循環(huán)小數(shù)。
那整數(shù)的除法運(yùn)算遇到除不盡的時(shí)候,結(jié)果難道不是浮點(diǎn)數(shù)嗎?我們來試一下:
11 / 4 # ==> 2
令很多初學(xué)者驚訝的是,Python的整數(shù)除法,即使除不盡,結(jié)果仍然是整數(shù),余數(shù)直接被扔掉。不過,Python提供了一個(gè)求余的運(yùn)算 % 可以計(jì)算余數(shù):(C語言與C++中用同樣的方法求余)
11 % 4 # ==> 3
如果我們要計(jì)算 11 / 4 的精確結(jié)果,按照“整數(shù)和浮點(diǎn)數(shù)混合運(yùn)算的結(jié)果是浮點(diǎn)數(shù)”的法則,把兩個(gè)數(shù)中的一個(gè)變成浮點(diǎn)數(shù)再運(yùn)算就沒問題了:
11.0 / 4 # ==> 2.75
布爾類型我們已經(jīng)了解了Python支持布爾類型的數(shù)據(jù),布爾類型只有True和False兩種值,但是布爾類型有以下幾種運(yùn)算:
與運(yùn)算:只有兩個(gè)布爾值都為 True 時(shí),計(jì)算結(jié)果才為 True。
True and True # ==> True
True and False # ==> False
False and True # ==> False
False and False # ==> False
或運(yùn)算:只要有一個(gè)布爾值為 True,計(jì)算結(jié)果就是 True。
True or True # ==> True
True or False # ==> True
False or True # ==> True
False or False # ==> False
非運(yùn)算:把True變?yōu)镕alse,或者把False變?yōu)門rue:
not True # ==> False
not False # ==> True
布爾運(yùn)算在計(jì)算機(jī)中用來做條件判斷,根據(jù)計(jì)算結(jié)果為True或者False,計(jì)算機(jī)可以自動(dòng)執(zhí)行不同的后續(xù)代碼。
在Python中,布爾類型還可以與其他數(shù)據(jù)類型做 and、or和not運(yùn)算,請(qǐng)看下面的代碼:
a = True
print a and 'a=T' or 'a=F'
計(jì)算結(jié)果不是布爾類型,而是字符串 'a=T',這是為什么呢?
因?yàn)镻ython把0、空字符串''和None看成 False,其他數(shù)值和非空字符串都看成 True,所以:
True and 'a=T' 計(jì)算結(jié)果是 'a=T'
繼續(xù)計(jì)算 'a=T' or 'a=F' 計(jì)算結(jié)果還是 'a=T'
要解釋上述結(jié)果,又涉及到 and 和 or 運(yùn)算的一條重要法則:短路計(jì)算。
1. 在計(jì)算 a and b 時(shí),如果 a 是 False,則根據(jù)與運(yùn)算法則,整個(gè)結(jié)果必定為 False,因此返回 a;如果 a 是 True,則整個(gè)計(jì)算結(jié)果必定取決與 b,因此返回 b。
2. 在計(jì)算 a or b 時(shí),如果 a 是 True,則根據(jù)或運(yùn)算法則,整個(gè)計(jì)算結(jié)果必定為 True,因此返回 a;如果 a 是 False,則整個(gè)計(jì)算結(jié)果必定取決于 b,因此返回 b。
所以Python解釋器在做布爾運(yùn)算時(shí),只要能提前確定計(jì)算結(jié)果,它就不會(huì)往后算了,直接返回結(jié)果。
創(chuàng)建listPython內(nèi)置的一種數(shù)據(jù)類型是列表:list。list是一種有序的集合,可以隨時(shí)添加和刪除其中的元素。
比如,列出班里所有同學(xué)的名字,就可以用一個(gè)list表示:
>>> ['Michael', 'Bob', 'Tracy']
['Michael', 'Bob', 'Tracy']
list是數(shù)學(xué)意義上的有序集合,也就是說,list中的元素是按照順序排列的。
構(gòu)造list非常簡單,按照上面的代碼,直接用 把list的所有元素都括起來,就是一個(gè)list對(duì)象。通常,我們會(huì)把list賦值給一個(gè)變量,這樣,就可以通過變量來引用list:
>>> classmates = ['Michael', 'Bob', 'Tracy']
>>> classmates # 打印classmates變量的內(nèi)容
['Michael', 'Bob', 'Tracy']
由于Python是動(dòng)態(tài)語言,所以list中包含的元素并不要求都必須是同一種數(shù)據(jù)類型,我們完全可以在list中包含各種數(shù)據(jù):
>>> L = ['Michael', 100, True]
一個(gè)元素也沒有的list,就是空list:
>>> empty_list =
按照索引訪問list由于list是一個(gè)有序集合,所以,我們可以用一個(gè)list按分?jǐn)?shù)從高到低表示出班里的3個(gè)同學(xué):
>>> L = ['Adam', 'Lisa', 'Bart']
那我們?nèi)绾螐膌ist中獲取指定第 N 名的同學(xué)呢?方法是通過索引來獲取list中的指定元素。
需要特別注意的是,索引從 0 開始,也就是說,第一個(gè)元素的索引是0,第二個(gè)元素的索引是1,以此類推。
因此,要打印第一名同學(xué)的名字,用 L[0]:
>>> print L[0]
Adam
要打印第二名同學(xué)的名字,用 L[1]:
>>> print L[1]
Lisa
要打印第三名同學(xué)的名字,用 L[2]:
>>> print L[2]
Bart
要打印第四名同學(xué)的名字,用 L[3]:
>>> print L[3]
File 'IndexError: list index out of range
報(bào)錯(cuò)了!IndexError意思就是索引超出了范圍,因?yàn)樯厦娴膌ist只有3個(gè)元素,有效的索引是 0,1,2。
所以,使用索引時(shí),千萬注意不要越界。
倒序訪問list我們還是用一個(gè)list按分?jǐn)?shù)從高到低表示出班里的3個(gè)同學(xué):
>>> L = ['Adam', 'Lisa', 'Bart']
這時(shí),老師說,請(qǐng)分?jǐn)?shù)最低的同學(xué)站出來。
要寫代碼完成這個(gè)任務(wù),我們可以先數(shù)一數(shù)這個(gè) list,發(fā)現(xiàn)它包含3個(gè)元素,因此,最后一個(gè)元素的索引是2:
>>> print L[2]
Bart
有沒有更簡單的方法?
有!
Bart同學(xué)是最后一名,俗稱倒數(shù)第一,所以,我們可以用 -1 這個(gè)索引來表示最后一個(gè)元素:
>>> print L[-1]
Bart
Bart同學(xué)表示躺槍。
類似的,倒數(shù)第二用 -2 表示,倒數(shù)第三用 -3 表示,倒數(shù)第四用 -4 表示:
>>> print L[-2]
Lisa
>>> print L[-3]
Adam
>>> print L[-4]
Traceback (most recent call last):
File 'IndexError: list index out of range
L[-4] 報(bào)錯(cuò)了,因?yàn)榈箶?shù)第四不存在,一共只有3個(gè)元素。
使用倒序索引時(shí),也要注意不要越界。
添加新元素現(xiàn)在,班里有3名同學(xué):
>>> L = ['Adam', 'Lisa', 'Bart']
今天,班里轉(zhuǎn)來一名新同學(xué) Paul,如何把新同學(xué)添加到現(xiàn)有的 list 中呢?
第一個(gè)辦法是用 list 的 append 方法,把新同學(xué)追加到 list 的末尾:
>>> L = ['Adam', 'Lisa', 'Bart']
>>> L.append('Paul')
>>> print L
['Adam', 'Lisa', 'Bart', 'Paul']
append總是把新的元素添加到 list 的尾部。
如果 Paul 同學(xué)表示自己總是考滿分,要求添加到第一的位置,怎么辦?
方法是用list的 insert方法,它接受兩個(gè)參數(shù),第一個(gè)參數(shù)是索引號(hào),第二個(gè)參數(shù)是待添加的新元素:
>>> L = ['Adam', 'Lisa', 'Bart']
>>> L.insert(0, 'Paul')
>>> print L
['Paul', 'Adam', 'Lisa', 'Bart']
L.insert(0, 'Paul') 的意思是,'Paul'將被添加到索引為 0 的位置上(也就是第一個(gè)),而原來索引為 0 的Adam同學(xué),以及后面的所有同學(xué),都自動(dòng)向后移動(dòng)一位。
從list刪除元素Paul同學(xué)剛來幾天又要轉(zhuǎn)走了,那么我們?cè)趺窗裀aul 從現(xiàn)有的list中刪除呢?
如果Paul同學(xué)排在最后一個(gè),我們可以用list的pop方法刪除:
>>> L = ['Adam', 'Lisa', 'Bart', 'Paul']
>>> L.pop
'Paul'
>>> print L
['Adam', 'Lisa', 'Bart']
pop方法總是刪掉list的最后一個(gè)元素,并且它還返回這個(gè)元素,所以我們執(zhí)行 L.pop 后,會(huì)打印出 'Paul'。(聯(lián)想append只在最后添加,pop只刪除最后)
如果Paul同學(xué)不是排在最后一個(gè)怎么辦?比如Paul同學(xué)排在第三:
>>> L = ['Adam', 'Lisa', 'Paul', 'Bart']
要把Paul踢出list,我們就必須先定位Paul的位置。由于Paul的索引是2,因此,用 pop(2)把Paul刪掉:
>>> L.pop(2)
'Paul'
>>> print L
替換元素假設(shè)現(xiàn)在班里仍然是3名同學(xué):
>>> L = ['Adam', 'Lisa', 'Bart']
現(xiàn)在,Bart同學(xué)要轉(zhuǎn)學(xué)走了,碰巧來了一個(gè)Paul同學(xué),要更新班級(jí)成員名單,我們可以先把Bart刪掉,再把Paul添加進(jìn)來。
另一個(gè)辦法是直接用Paul把Bart給替換掉:
>>> L[2] = 'Paul'
>>> print L
L = ['Adam', 'Lisa', 'Paul']
對(duì)list中的某一個(gè)索引賦值,就可以直接用新的元素替換掉原來的元素,list包含的元素個(gè)數(shù)保持不變。
由于Bart還可以用 -1 做索引,因此,下面的代碼也可以完成同樣的替換工作:
>>> L[-1] = 'Paul'
創(chuàng)建tupletuple是另一種有序的列表,中文翻譯為“ 元組 ”。tuple 和 list 非常類似,但是,tuple一旦創(chuàng)建完畢,就不能修改了。list創(chuàng)建后還是可以修改的哦!
同樣是表示班里同學(xué)的名稱,用tuple表示如下:
>>> t = ('Adam', 'Lisa', 'Bart')
創(chuàng)建tuple和創(chuàng)建list唯一不同之處是用替代了。
現(xiàn)在,這個(gè) t 就不能改變了,tuple沒有 append方法,也沒有insert和pop方法。所以,新同學(xué)沒法直接往 tuple 中添加,老同學(xué)想退出 tuple 也不行。
獲取 tuple 元素的方式和 list 是一模一樣的,我們可以正常使用 t[0],t[-1]等索引方式訪問元素,但是不能賦值成別的元素,不信可以試試:
>>> t[0] = 'Paul'
File 'TypeError: 'tuple' object does not support item assignment
創(chuàng)建單元素tupletuple和list一樣,可以包含 0 個(gè)、1個(gè)和任意多個(gè)元素。
包含多個(gè)元素的 tuple,前面我們已經(jīng)創(chuàng)建過了。
包含 0 個(gè)元素的 tuple,也就是空tuple,直接用 表示:
>>> t =
>>> print t
創(chuàng)建包含1個(gè)元素的 tuple 呢?來試試:
>>> t = (1)
>>> print t
1
好像哪里不對(duì)!t 不是 tuple ,而是整數(shù)1。為什么呢?
因?yàn)?/span>既可以表示tuple,又可以作為括號(hào)表示運(yùn)算時(shí)的優(yōu)先級(jí),結(jié)果 (1) 被Python解釋器計(jì)算出結(jié)果 1,導(dǎo)致我們得到的不是tuple,而是整數(shù) 1。
正是因?yàn)橛枚x單元素的tuple有歧義,所以 Python 規(guī)定,單元素 tuple 要多加一個(gè)逗號(hào)“,”,這樣就避免了歧義:
>>> t = (1,)
>>> print t
(1,)
Python在打印單元素tuple時(shí),也自動(dòng)添加了一個(gè)“,”,為了更明確地告訴你這是一個(gè)tuple。
多元素 tuple 加不加這個(gè)額外的“,”效果是一樣的:
>>> t = (1, 2, 3,)
>>> print t
(1, 2, 3)
“可變”的tuple前面我們看到了tuple一旦創(chuàng)建就不能修改。現(xiàn)在,我們來看一個(gè)“可變”的tuple:
>>> t = ('a', 'b', ['A', 'B'])
注意到 t 有 3 個(gè)元素:'a','b'和一個(gè)list:['A', 'B']。list作為一個(gè)整體是tuple的第3個(gè)元素。list對(duì)象可以通過 t[2] 拿到:
>>> print t
('a', 'b', ['X', 'Y'])
不是說tuple一旦定義后就不可變了嗎?怎么現(xiàn)在又變了?
別急,我們先看看定義的時(shí)候tuple包含的3個(gè)元素:
當(dāng)我們把list的元素'A'和'B'修改為'X'和'Y'后,tuple變?yōu)椋?/span>
表面上看,tuple的元素確實(shí)變了,但其實(shí)變的不是 tuple 的元素,而是list的元素。
tuple一開始指向的list并沒有改成別的list,所以,tuple所謂的“不變”是說,tuple的每個(gè)元素,指向永遠(yuǎn)不變。即指向'a',就不能改成指向'b',指向一個(gè)list,就不能改成指向其他對(duì)象,但指向的這個(gè)list本身是可變的!
理解了“指向不變”后,要?jiǎng)?chuàng)建一個(gè)內(nèi)容也不變的tuple怎么做?那就必須保證tuple的每一個(gè)元素本身也不能變。
if語句計(jì)算機(jī)之所以能做很多自動(dòng)化的任務(wù),因?yàn)樗梢宰约鹤鰲l件判斷。
比如,輸入用戶年齡,根據(jù)年齡打印不同的內(nèi)容,在Python程序中,可以用if語句實(shí)現(xiàn):
age = 20
if age >= 18:
print 'your age is', age
print 'adult'
print 'END'
注意: Python代碼的縮進(jìn)規(guī)則。具有相同縮進(jìn)的代碼被視為代碼塊,上面的3,4行 print 語句就構(gòu)成一個(gè)代碼塊(但不包括第5行的print)。如果 if 語句判斷為 True,就會(huì)執(zhí)行這個(gè)代碼塊。
縮進(jìn)請(qǐng)嚴(yán)格按照Python的習(xí)慣寫法:4個(gè)空格,不要使用Tab,更不要混合Tab和空格,否則很容易造成因?yàn)榭s進(jìn)引起的語法錯(cuò)誤。
注意: if 語句后接表達(dá)式,然后用:表示代碼塊開始。
如果你在Python交互環(huán)境下敲代碼,還要特別留意縮進(jìn),并且退出縮進(jìn)需要多敲一行回車:
>>> age = 20
>>> if age >= 18:
... print 'your age is', age
... print 'adult'
...
your age is 20
adult
if-elseprint 'adult'
如果我們想判斷年齡在18歲以下時(shí),打印出 'teenager',怎么辦?
print 'teenager'
或者用 not 運(yùn)算:
if not age >= 18:
細(xì)心的同學(xué)可以發(fā)現(xiàn),這兩種條件判斷是“非此即彼”的,要么符合條件1,要么符合條件2,因此,完全可以用一個(gè) if ... else ... 語句把它們統(tǒng)一起來:
if age >= 18:
print 'adult'
else:
利用 if ... else ... 語句,我們可以根據(jù)條件表達(dá)式的值為 True 或者False ,分別執(zhí)行 if 代碼塊或者 else 代碼塊。
注意: else 后面有個(gè)“:”。
if-elif-else有的時(shí)候,一個(gè) if ... else ... 還不夠用。比如,根據(jù)年齡的劃分:
條件1:18歲或以上:adult
條件2:6歲或以上:teenager
條件3:6歲以下:kid
我們可以用一個(gè) if age >= 18 判斷是否符合條件1,如果不符合,再通過一個(gè) if 判斷 age >= 6 來判斷是否符合條件2,否則,執(zhí)行條件3:
else:
print 'kid'
這樣寫出來,我們就得到了一個(gè)兩層嵌套的 if ... else ... 語句。這個(gè)邏輯沒有問題,但是,如果繼續(xù)增加條件,比如3歲以下是 baby:
if age >= 18:
else:
print 'baby'
這種縮進(jìn)只會(huì)越來越多,代碼也會(huì)越來越難看。
要避免嵌套結(jié)構(gòu)的 if ... else ...,我們可以用 if ... 多個(gè)elif ... else ...的結(jié)構(gòu),一次寫完所有的規(guī)則:
if age >= 18:
print 'kid'else:
print 'baby'
elif 意思就是 else if。這樣一來,我們就寫出了結(jié)構(gòu)非常清晰的一系列條件判斷。
特別注意: 這一系列條件判斷會(huì)從上到下依次判斷,如果某個(gè)判斷為 True,執(zhí)行完對(duì)應(yīng)的代碼塊,后面的條件判斷就直接忽略,不再執(zhí)行了。
for循環(huán)list或tuple可以表示一個(gè)有序集合。如果我們想依次訪問一個(gè)list中的每一個(gè)元素呢?比如 list:
L = ['Adam', 'Lisa', 'Bart']
print L[0]
print L[1]
print L[2]
如果list只包含幾個(gè)元素,這樣寫還行,如果list包含1萬個(gè)元素,我們就不可能寫1萬行print。
這時(shí),循環(huán)就派上用場了。
Python的 for 循環(huán)就可以依次把list或tuple的每個(gè)元素迭代出來:
L = ['Adam', 'Lisa', 'Bart']for namein L:
print name
注意: name 這個(gè)變量是在 for 循環(huán)中定義的,意思是,依次取出list中的每一個(gè)元素,并把元素賦值給 name,然后執(zhí)行for循環(huán)體(就是縮進(jìn)的代碼塊)。
這樣一來,遍歷一個(gè)list或tuple就非常容易了。
while循環(huán)和 for 循環(huán)不同的另一種循環(huán)是 while 循環(huán),while 循環(huán)不會(huì)迭代 list 或 tuple 的元素,而是根據(jù)表達(dá)式判斷循環(huán)是否結(jié)束。
比如要從 0 開始打印不大于 N 的整數(shù):
N = 10
x = 0while x <>:
print x
x = x + 1
while循環(huán)每次先判斷 x <>,如果為True,則執(zhí)行循環(huán)體的代碼塊,否則,退出循環(huán)。
在循環(huán)體內(nèi),x = x + 1 會(huì)讓 x 不斷增加,最終因?yàn)?/span> x < n="">不成立而退出循環(huán)。
如果沒有這一個(gè)語句,while循環(huán)在判斷 x < n="">,就會(huì)無限循環(huán)下去,變成死循環(huán),所以要特別留意while循環(huán)的退出條件。
break退出循環(huán)用 for 循環(huán)或者 while 循環(huán)時(shí),如果要在循環(huán)體內(nèi)直接退出循環(huán),可以使用 break 語句。
if x > 100:
break
print sum
咋一看, while True 就是一個(gè)死循環(huán),但是在循環(huán)體內(nèi),我們還判斷了 x > 100 條件成立時(shí),用break語句退出循環(huán),這樣也可以實(shí)現(xiàn)循環(huán)的結(jié)束。
continue繼續(xù)循環(huán)在循環(huán)過程中,可以用break退出當(dāng)前循環(huán),還可以用continue跳過后續(xù)循環(huán)代碼,繼續(xù)下一次循環(huán)。
假設(shè)我們已經(jīng)寫好了利用for循環(huán)計(jì)算平均分的代碼:
L = [75, 98, 59, 81, 66, 43, 69, 85]
sum = 0.0
n = 0for xin L:
sum = sum + x
n = n + 1
print sum / n
現(xiàn)在老師只想統(tǒng)計(jì)及格分?jǐn)?shù)的平均分,就要把 x < 60="" 的分?jǐn)?shù)剔除掉,這時(shí),利用="" continue,可以做到當(dāng)="" x=""><>
for x in L:
if x <>
continue
sum = sum + x
n = n + 1
多重循環(huán)
for xin ['A', 'B', 'C']:
x 每循環(huán)一次,y 就會(huì)循環(huán) 3 次,這樣,我們可以打印出一個(gè)全排列:
A1
A2A3B1B2B3C1C2C3什么是dict
或者考試的成績列表:
[95, 85, 59]
但是,要根據(jù)名字找到對(duì)應(yīng)的成績,用兩個(gè) list 表示就不方便。
如果把名字和分?jǐn)?shù)關(guān)聯(lián)起來,組成類似的查找表:
'Adam' ==> 95
'Lisa' ==> 85
'Bart' ==> 59
給定一個(gè)名字,就可以直接查到分?jǐn)?shù)。
Python的 dict 就是專門干這件事的。用 dict 表示“名字”-“成績”的查找表如下:
d = {
'Adam': 95,
'Lisa': 85,
'Bart': 59
}
我們把名字稱為key,對(duì)應(yīng)的成績稱為value,dict就是通過 key 來查找 value。
花括號(hào) {} 表示這是一個(gè)dict,然后按照 key: value, 寫出來即可。最后一個(gè) key: value 的逗號(hào)可以省略。
由于dict也是集合,len 函數(shù)可以計(jì)算任意集合的大?。?/span>
>>> len(d)
3
注意: 一個(gè) key-value 算一個(gè),因此,dict大小為3。
訪問dict我們已經(jīng)能創(chuàng)建一個(gè)dict,用于表示名字和成績的對(duì)應(yīng)關(guān)系:
d = {
'Adam': 95,
'Lisa': 85,
'Bart': 59
}
那么,如何根據(jù)名字來查找對(duì)應(yīng)的成績呢?
可以簡單地使用 d[key] 的形式來查找對(duì)應(yīng)的 value,這和 list 很像,不同之處是,list 必須使用索引返回對(duì)應(yīng)的元素,而dict使用key:
>>> print d['Adam']
95
>>> print d['Paul']
Traceback (most recent call last):
File 'index.py', line 11, in
print d['Paul']
KeyError: 'Paul'
注意: 通過 key 訪問 dict 的value,只要 key 存在,dict就返回對(duì)應(yīng)的value。如果key不存在,會(huì)直接報(bào)錯(cuò):KeyError。
要避免 KeyError 發(fā)生,有兩個(gè)辦法:
一是先判斷一下 key 是否存在,用 in 操作符:
if 'Paul' in d:
print d['Paul']
如果 'Paul' 不存在,if語句判斷為False,自然不會(huì)執(zhí)行 print d['Paul'] ,從而避免了錯(cuò)誤。
二是使用dict本身提供的一個(gè) get 方法,在Key不存在的時(shí)候,返回None:
>>> print d.get('Bart')
59
>>> print d.get('Paul')
None
dict的特點(diǎn)dict的第一個(gè)特點(diǎn)是查找速度快,無論dict有10個(gè)元素還是10萬個(gè)元素,查找速度都一樣。而list的查找速度隨著元素增加而逐漸下降。
不過dict的查找速度快不是沒有代價(jià)的,dict的缺點(diǎn)是占用內(nèi)存大,還會(huì)浪費(fèi)很多內(nèi)容,list正好相反,占用內(nèi)存小,但是查找速度慢。
由于dict是按 key 查找,所以,在一個(gè)dict中,key不能重復(fù)。
dict的第二個(gè)特點(diǎn)就是存儲(chǔ)的key-value序?qū)κ?/span>沒有順序的!這和list不一樣:
d = {
'Adam': 95,
'Lisa': 85,
'Bart': 59
}
當(dāng)我們?cè)噲D打印這個(gè)dict時(shí):
>>> print d
{'Lisa': 85, 'Adam': 95, 'Bart': 59}
打印的順序不一定是我們創(chuàng)建時(shí)的順序,而且,不同的機(jī)器打印的順序都可能不同,這說明dict內(nèi)部是無序的,不能用dict存儲(chǔ)有序的集合。
dict的第三個(gè)特點(diǎn)是作為 key 的元素必須不可變,Python的基本類型如字符串、整數(shù)、浮點(diǎn)數(shù)都是不可變的,都可以作為 key。但是list是可變的,就不能作為 key。
可以試試用list作為key時(shí)會(huì)報(bào)什么樣的錯(cuò)誤。
不可變這個(gè)限制僅作用于key,value是否可變無所謂:
{
'123': [1, 2, 3], # key 是 str,value是list
123: '123', # key 是 int,value 是 str
('a', 'b'): True # key 是 tuple,并且tuple的每個(gè)元素都是不可變對(duì)象,value是 boolean
}
最常用的key還是字符串,因?yàn)橛闷饋碜罘奖恪?/span>
更新dictdict是可變的,也就是說,我們可以隨時(shí)往dict中添加新的 key-value。比如已有dict:
d = {
'Adam': 95,
'Lisa': 85,
'Bart': 59
}
要把新同學(xué)'Paul'的成績 72 加進(jìn)去,用賦值語句:
>>> d['Paul'] = 72
再看看dict的內(nèi)容:
>>> print d
{'Lisa': 85, 'Paul': 72, 'Adam': 95, 'Bart': 59}
如果 key 已經(jīng)存在,則賦值會(huì)用新的 value 替換掉原來的 value:
>>> d['Bart'] = 60
>>> print d
{'Lisa': 85, 'Paul': 72, 'Adam': 95, 'Bart': 60}
遍歷dict由于dict也是一個(gè)集合,所以,遍歷dict和遍歷list類似,都可以通過 for 循環(huán)實(shí)現(xiàn)。
直接使用for循環(huán)可以遍歷 dict 的 key:
>>> d = { 'Adam': 95, 'Lisa': 85, 'Bart': 59 }
>>> for key in d:
... print key
...
Lisa
Adam
Bart
由于通過 key 可以獲取對(duì)應(yīng)的 value,因此,在循環(huán)體內(nèi),可以獲取到value的值。
什么是setdict的作用是建立一組 key 和一組 value 的映射關(guān)系,dict的key是不能重復(fù)的。
有的時(shí)候,我們只想要 dict 的 key,不關(guān)心 key 對(duì)應(yīng)的 value,目的就是保證這個(gè)集合的元素不會(huì)重復(fù),這時(shí),set就派上用場了。
set 持有一系列元素,這一點(diǎn)和 list 很像,但是set的元素沒有重復(fù),而且是無序的,這點(diǎn)和 dict 的 key很像。
創(chuàng)建 set 的方式是調(diào)用 set 并傳入一個(gè) list,list的元素將作為set的元素:
>>> s = set(['A', 'B', 'C'])
可以查看 set 的內(nèi)容:
>>> print s
set(['A', 'C', 'B'])
請(qǐng)注意,上述打印的形式類似 list, 但它不是 list,仔細(xì)看還可以發(fā)現(xiàn),打印的順序和原始 list 的順序有可能是不同的,因?yàn)閟et內(nèi)部存儲(chǔ)的元素是無序的。
因?yàn)?/span>set不能包含重復(fù)的元素,所以,當(dāng)我們傳入包含重復(fù)元素的 list 會(huì)怎么樣呢?
>>> s = set(['A', 'B', 'C', 'C'])
>>> print s
set(['A', 'C', 'B'])
>>> len(s)
3
結(jié)果顯示,set會(huì)自動(dòng)去掉重復(fù)的元素,原來的list有4個(gè)元素,但set只有3個(gè)元素。
訪問set由于set存儲(chǔ)的是無序集合,所以我們沒法通過索引來訪問。
訪問 set中的某個(gè)元素實(shí)際上就是判斷一個(gè)元素是否在set中。
例如,存儲(chǔ)了班里同學(xué)名字的set:
>>> s = set(['Adam', 'Lisa', 'Bart', 'Paul'])
我們可以用 in 操作符判斷:
Bart是該班的同學(xué)嗎?
>>> 'Bart' in s
True
Bill是該班的同學(xué)嗎?
>>> 'Bill' in s
False
bart是該班的同學(xué)嗎?
>>> 'bart' in s
False
看來大小寫很重要,'Bart' 和 'bart'被認(rèn)為是兩個(gè)不同的元素。
set的特點(diǎn)set的內(nèi)部結(jié)構(gòu)和dict很像,唯一區(qū)別是不存儲(chǔ)value,因此,判斷一個(gè)元素是否在set中速度很快。
set存儲(chǔ)的元素和dict的key類似,必須是不變對(duì)象,因此,任何可變對(duì)象是不能放入set中的。
最后,set存儲(chǔ)的元素也是沒有順序的。
set的這些特點(diǎn),可以應(yīng)用在哪些地方呢?
星期一到星期日可以用字符串'MON', 'TUE', ... 'SUN'表示。
假設(shè)我們讓用戶輸入星期一至星期日的某天,如何判斷用戶的輸入是否是一個(gè)有效的星期呢?
可以用 if 語句判斷,但這樣做非常繁瑣:
x = '???'# 用戶輸入的字符串if x!= 'MON' and x!= 'TUE' and x!= 'WED' ... and x!= 'SUN':
print 'input error'else:
注意:if 語句中的...表示沒有列出的其它星期名稱,測試時(shí),請(qǐng)輸入完整。
如果事先創(chuàng)建好一個(gè)set,包含'MON' ~ 'SUN':
weekdays = set(['MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT', 'SUN'])
再判斷輸入是否有效,只需要判斷該字符串是否在set中:
x = '???'# 用戶輸入的字符串if xin weekdays:
print 'input ok'else:
print 'input error'
這樣一來,代碼就簡單多了。
遍歷set由于 set 也是一個(gè)集合,所以,遍歷 set 和遍歷 list 類似,都可以通過 for 循環(huán)實(shí)現(xiàn)。
直接使用 for 循環(huán)可以遍歷 set 的元素:
>>> s = set(['Adam', 'Lisa', 'Bart'])
>>> for namein s:
... print name
...
Lisa
Adam
Bart
注意: 觀察 for 循環(huán)在遍歷set時(shí),元素的順序和list的順序很可能是不同的,而且不同的機(jī)器上運(yùn)行的結(jié)果也可能不同。
更新set由于set存儲(chǔ)的是一組不重復(fù)的無序元素,因此,更新set主要做兩件事:
添加元素時(shí),用set的add方法:
>>> s = set([1, 2, 3])
>>> s.add(4)
>>> print s
set([1, 2, 3, 4])
如果添加的元素已經(jīng)存在于set中,add不會(huì)報(bào)錯(cuò),但是不會(huì)加進(jìn)去了:
>>> s.add(3)
>>> print s
set([1, 2, 3])
刪除set中的元素時(shí),用set的remove方法:
>>> s = set([1, 2, 3, 4])
>>> s.remove(4)
>>> print s
set([1, 2, 3])
如果刪除的元素不存在set中,remove會(huì)報(bào)錯(cuò):
>>> s.remove(4)
File 'KeyError: 4
所以用add可以直接添加,而remove前需要判斷。
什么是函數(shù)我們知道圓的面積計(jì)算公式為:
S = πr2
當(dāng)我們知道半徑r的值時(shí),就可以根據(jù)公式計(jì)算出面積。假設(shè)我們需要計(jì)算3個(gè)不同大小的圓的面積:
r1 = 12.34
r2 = 9.08
r3 = 73.1
s1 = 3.14 * r1 * r1
s2 = 3.14 * r2 * r2
s3 = 3.14 * r3 * r3
當(dāng)代碼出現(xiàn)有規(guī)律的重復(fù)的時(shí)候,你就需要當(dāng)心了,每次寫3.14 * x * x不僅很麻煩,而且,如果要把3.14改成3.14159265359的時(shí)候,得全部替換。
有了函數(shù),我們就不再每次寫s = 3.14 * x * x,而是寫成更有意義的函數(shù)調(diào)用 s = area_of_circle(x),而函數(shù) area_of_circle 本身只需要寫一次,就可以多次調(diào)用。
抽象是數(shù)學(xué)中非常常見的概念。舉個(gè)例子:
計(jì)算數(shù)列的和,比如:1 + 2 + 3 + ... + 100,寫起來十分不方便,于是數(shù)學(xué)家發(fā)明了求和符號(hào)∑,可以把1 + 2 + 3 + ... + 100記作:
100∑n
n=1
這種抽象記法非常強(qiáng)大,因?yàn)槲覀兛吹健凭涂梢岳斫獬汕蠛停皇沁€原成低級(jí)的加法運(yùn)算。
而且,這種抽象記法是可擴(kuò)展的,比如:
n=1
還原成加法運(yùn)算就變成了:
(1 x 1 + 1) + (2 x 2 + 1) + (3 x 3 + 1) + ... + (100 x 100 + 1)
可見,借助抽象,我們才能不關(guān)心底層的具體計(jì)算過程,而直接在更高的層次上思考問題。
寫計(jì)算機(jī)程序也是一樣,函數(shù)就是最基本的一種代碼抽象的方式。
Python不但能非常靈活地定義函數(shù),而且本身內(nèi)置了很多有用的函數(shù),可以直接調(diào)用。
調(diào)用函數(shù)Python內(nèi)置了很多有用的函數(shù),我們可以直接調(diào)用。
可以直接從Python的官方網(wǎng)站查看文檔:
http://docs.python.org/2/library/functions.html#abs
調(diào)用 abs 函數(shù):
>>> abs(100)
100
>>> abs(-20)
20
>>> abs(12.34)
12.34
File 'TypeError: abs takes exactly one argument (2 given)
File 'TypeError: bad operand type for abs: 'str'
而比較函數(shù) cmp(x, y) 就需要兩個(gè)參數(shù),如果 x,返回 -1,如果x==y,返回 0,如果 x>y,返回 1:
>>> cmp(1, 2)
-1
>>> cmp(2, 1)
1
>>> cmp(3, 3)
0
Python內(nèi)置的常用函數(shù)還包括數(shù)據(jù)類型轉(zhuǎn)換函數(shù),比如 int函數(shù)可以把其他數(shù)據(jù)類型轉(zhuǎn)換為整數(shù):
>>> int('123')
123
>>> int(12.34)
12
str函數(shù)把其他類型轉(zhuǎn)換成 str:
>>> str(123)
'123'
>>> str(1.23)
'1.23'
編寫函數(shù)在Python中,定義一個(gè)函數(shù)要使用 def 語句,依次寫出函數(shù)名、括號(hào)、括號(hào)中的參數(shù)和冒號(hào):,然后,在縮進(jìn)塊中編寫函數(shù)體,函數(shù)的返回值用 return 語句返回。
我們以自定義一個(gè)求絕對(duì)值的 my_abs 函數(shù)為例:
def my_abs(x):
if x >= 0:
return x
else:
return -x
請(qǐng)注意,函數(shù)體內(nèi)部的語句在執(zhí)行時(shí),一旦執(zhí)行到return時(shí),函數(shù)就執(zhí)行完畢,并將結(jié)果返回。因此,函數(shù)內(nèi)部通過條件判斷和循環(huán)可以實(shí)現(xiàn)非常復(fù)雜的邏輯。
如果沒有return語句,函數(shù)執(zhí)行完畢后也會(huì)返回結(jié)果,只是結(jié)果為 None。
return None可以簡寫為return。
返回多值函數(shù)可以返回多個(gè)值嗎?答案是肯定的。
比如在游戲中經(jīng)常需要從一個(gè)點(diǎn)移動(dòng)到另一個(gè)點(diǎn),給出坐標(biāo)、位移和角度,就可以計(jì)算出新的坐標(biāo):
# math包提供了sin和 cos函數(shù),我們先用import引用它:
import math
def move(x, y, step, angle):
nx = x + step * math.cos(angle)
ny = y - step * math.sin(angle)
return nx, ny
這樣我們就可以同時(shí)獲得返回值:
>>> x, y = move(100, 100, 60, math.pi / 6)
>>> print x, y
151.961524227 70.0
但其實(shí)這只是一種假象,Python函數(shù)返回的仍然是單一值:
>>> r = move(100, 100, 60, math.pi / 6)
>>> print r
(151.96152422706632, 70.0)
用print打印返回結(jié)果,原來返回值是一個(gè)tuple!
但是,在語法上,返回一個(gè)tuple可以省略括號(hào),而多個(gè)變量可以同時(shí)接收一個(gè)tuple,按位置賦給對(duì)應(yīng)的值,所以,Python的函數(shù)返回多值其實(shí)就是返回一個(gè)tuple,但寫起來更方便。
遞歸函數(shù)在函數(shù)內(nèi)部,可以調(diào)用其他函數(shù)。如果一個(gè)函數(shù)在內(nèi)部調(diào)用自身本身,這個(gè)函數(shù)就是遞歸函數(shù)。
舉個(gè)例子,我們來計(jì)算階乘 n! = 1 * 2 * 3 * ... * n,用函數(shù) fact(n)表示,可以看出:
fact(n) = n! = 1 * 2 * 3 * ... * (n-1) * n = (n-1)! * n = fact(n-1) * n
所以,fact(n)可以表示為 n * fact(n-1),只有n=1時(shí)需要特殊處理。
于是,fact(n)用遞歸的方式寫出來就是:
def fact(n):
if n==1:
return 1
return n * fact(n - 1)
上面就是一個(gè)遞歸函數(shù)??梢栽囋嚕?/span>
>>> fact(1)
1
>>> fact(5)
120
>>> fact(100)
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000L
如果我們計(jì)算fact(5),可以根據(jù)函數(shù)定義看到計(jì)算過程如下:
===> fact(5)
===> 5 * fact(4)
===> 5 * (4 * fact(3))
===> 5 * (4 * (3 * fact(2)))
===> 5 * (4 * (3 * (2 * fact(1))))
===> 5 * (4 * (3 * (2 * 1)))
===> 5 * (4 * (3 * 2))
===> 5 * (4 * 6)
===> 5 * 24
===> 120
遞歸函數(shù)的優(yōu)點(diǎn)是定義簡單,邏輯清晰。理論上,所有的遞歸函數(shù)都可以寫成循環(huán)的方式,但循環(huán)的邏輯不如遞歸清晰。
使用遞歸函數(shù)需要注意防止棧溢出。在計(jì)算機(jī)中,函數(shù)調(diào)用是通過棧(stack)這種數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的,每當(dāng)進(jìn)入一個(gè)函數(shù)調(diào)用,棧就會(huì)加一層棧幀,每當(dāng)函數(shù)返回,棧就會(huì)減一層棧幀。由于棧的大小不是無限的,所以,遞歸調(diào)用的次數(shù)過多,會(huì)導(dǎo)致棧溢出。可以試試計(jì)算 fact(10000)。
定義默認(rèn)參數(shù)定義函數(shù)的時(shí)候,還可以有默認(rèn)參數(shù)。
例如Python自帶的 int 函數(shù),其實(shí)就有兩個(gè)參數(shù),我們既可以傳一個(gè)參數(shù),又可以傳兩個(gè)參數(shù):
>>> int('123')
123
>>> int('123', 8)
83
int函數(shù)的第二個(gè)參數(shù)是轉(zhuǎn)換進(jìn)制,如果不傳,默認(rèn)是十進(jìn)制 (base=10),如果傳了,就用傳入的參數(shù)。
可見,函數(shù)的默認(rèn)參數(shù)的作用是簡化調(diào)用,你只需要把必須的參數(shù)傳進(jìn)去。但是在需要的時(shí)候,又可以傳入額外的參數(shù)來覆蓋默認(rèn)參數(shù)值。
我們來定義一個(gè)計(jì)算 x 的N次方的函數(shù):
def power(x, n):
s = 1
while n > 0:
n = n - 1
s = s * x
return s
假設(shè)計(jì)算平方的次數(shù)最多,我們就可以把 n 的默認(rèn)值設(shè)定為 2:
def power(x, n=2):
s = 1
while n > 0:
n = n - 1
s = s * x
return s
這樣一來,計(jì)算平方就不需要傳入兩個(gè)參數(shù)了:
>>> power(5)
25
def fn2(a=1, b):
pass
定義可變參數(shù)如果想讓一個(gè)函數(shù)能接受任意個(gè)參數(shù),我們就可以定義一個(gè)可變參數(shù):
def fn(*args):
print args
可變參數(shù)的名字前面有個(gè) * 號(hào),我們可以傳入0個(gè)、1個(gè)或多個(gè)參數(shù)給可變參數(shù):
>>> fn
>>> fn('a')
('a',)
>>> fn('a', 'b')
('a', 'b')
>>> fn('a', 'b', 'c')
('a', 'b', 'c')
可變參數(shù)也不是很神秘,Python解釋器會(huì)把傳入的一組參數(shù)組裝成一個(gè)tuple傳遞給可變參數(shù),因此,在函數(shù)內(nèi)部,直接把變量 args看成一個(gè) tuple 就好了。
定義可變參數(shù)的目的也是為了簡化調(diào)用。假設(shè)我們要計(jì)算任意個(gè)數(shù)的平均值,就可以定義一個(gè)可變參數(shù):
def average(*args):
...
這樣,在調(diào)用的時(shí)候,可以這樣寫:
>>> average
0
>>> average(1, 2)
1.5
>>> average(1, 2, 2, 3, 4)
2.4
對(duì)list進(jìn)行切片取一個(gè)list的部分元素是非常常見的操作。比如,一個(gè)list如下:
>>> L = ['Adam', 'Lisa', 'Bart', 'Paul']
取前3個(gè)元素,應(yīng)該怎么做?
笨辦法:
>>> [L[0], L[1], L[2]]
['Adam', 'Lisa', 'Bart']
之所以是笨辦法是因?yàn)閿U(kuò)展一下,取前N個(gè)元素就沒轍了。
取前N個(gè)元素,也就是索引為0-(N-1)的元素,可以用循環(huán):
>>> r =
>>> n = 3
>>> for i in range(n):
... r.append(L[i])
...
>>> r
['Adam', 'Lisa', 'Bart']
對(duì)這種經(jīng)常取指定索引范圍的操作,用循環(huán)十分繁瑣,因此,Python提供了切片(Slice)操作符,能大大簡化這種操作。
對(duì)應(yīng)上面的問題,取前3個(gè)元素,用一行代碼就可以完成切片:
>>> L[0:3]
['Adam', 'Lisa', 'Bart']
L[0:3]表示,從索引0開始取,直到索引3為止,但不包括索引3。即索引0,1,2,正好是3個(gè)元素。
如果第一個(gè)索引是0,還可以省略:
>>> L[:3]
['Adam', 'Lisa', 'Bart']
也可以從索引1開始,取出2個(gè)元素出來:
>>> L[1:3]
['Lisa', 'Bart']
只用一個(gè) : ,表示從頭到尾:
>>> L[:]
['Adam', 'Lisa', 'Bart', 'Paul']
因此,L[:]實(shí)際上復(fù)制出了一個(gè)新list。
切片操作還可以指定第三個(gè)參數(shù):
>>> L[::2]
['Adam', 'Bart']
第三個(gè)參數(shù)表示每N個(gè)取一個(gè),上面的 L[::2] 會(huì)每兩個(gè)元素取出一個(gè)來,也就是隔一個(gè)取一個(gè)。
把list換成tuple,切片操作完全相同,只是切片的結(jié)果也變成了tuple。
倒序切片對(duì)于list,既然Python支持L[-1]取倒數(shù)第一個(gè)元素,那么它同樣支持倒數(shù)切片,試試:
>>> L = ['Adam', 'Lisa', 'Bart', 'Paul']
>>> L[-2:]
['Bart', 'Paul']
>>> L[:-2]
['Adam', 'Lisa']
>>> L[-3:-1]
['Lisa', 'Bart']
>>> L[-4:-1:2]
['Adam', 'Bart']
記住倒數(shù)第一個(gè)元素的索引是-1。倒序切片包含起始索引,不包含結(jié)束索引。
對(duì)字符串切片字符串 'xxx'和 Unicode字符串 u'xxx'也可以看成是一種list,每個(gè)元素就是一個(gè)字符。因此,字符串也可以用切片操作,只是操作結(jié)果仍是字符串:
>>> 'ABCDEFG'[:3]
'ABC'
>>> 'ABCDEFG'[-3:]
'EFG'
>>> 'ABCDEFG'[::2]
'ACEG'
在很多編程語言中,針對(duì)字符串提供了很多各種截取函數(shù),其實(shí)目的就是對(duì)字符串切片。Python沒有針對(duì)字符串的截取函數(shù),只需要切片一個(gè)操作就可以完成,非常簡單。
什么是迭代在Python中,如果給定一個(gè)list或tuple,我們可以通過for循環(huán)來遍歷這個(gè)list或tuple,這種遍歷我們成為迭代(Iteration)。
在Python中,迭代是通過 for ... in 來完成的,而很多語言比如C或者Java,迭代list是通過下標(biāo)完成的,比如Java代碼:
for (i=0; i
n = list[i];
}
可以看出,Python的for循環(huán)抽象程度要高于Java的for循環(huán)。
因?yàn)?Python 的 for循環(huán)不僅可以用在list或tuple上,還可以作用在其他任何可迭代對(duì)象上。
因此,迭代操作就是對(duì)于一個(gè)集合,無論該集合是有序還是無序,我們用 for 循環(huán)總是可以依次取出集合的每一個(gè)元素。
注意: 集合是指包含一組元素的數(shù)據(jù)結(jié)構(gòu),我們已經(jīng)介紹的包括:
1. 有序集合:list,tuple,str和unicode;
2. 無序集合:set
3. 無序集合并且具有 key-value 對(duì):dict
而迭代是一個(gè)動(dòng)詞,它指的是一種操作,在Python中,就是 for 循環(huán)。
迭代與按下標(biāo)訪問數(shù)組最大的不同是,后者是一種具體的迭代實(shí)現(xiàn)方式,而前者只關(guān)心迭代結(jié)果,根本不關(guān)心迭代內(nèi)部是如何實(shí)現(xiàn)的。
索引迭代Python中,迭代永遠(yuǎn)是取出元素本身,而非元素的索引。
對(duì)于有序集合,元素確實(shí)是有索引的。有的時(shí)候,我們確實(shí)想在 for 循環(huán)中拿到索引,怎么辦?
方法是使用 enumerate 函數(shù):
... print index, '-', name
...
0 - Adam
1 - Lisa
2 - Bart
3 - Paul
使用 enumerate 函數(shù),我們可以在for循環(huán)中同時(shí)綁定索引index和元素name。但是,這不是 enumerate 的特殊語法。實(shí)際上,enumerate 函數(shù)把:
[(0, 'Adam'), (1, 'Lisa'), (2, 'Bart'), (3, 'Paul')]
index = t[0]
name = t[1]
print index, '-', name
如果我們知道每個(gè)tuple元素都包含兩個(gè)元素,for循環(huán)又可以進(jìn)一步簡寫為:
for index, namein enumerate(L):
print index, '-', name
這樣不但代碼更簡單,而且還少了兩條賦值語句。
可見,索引迭代也不是真的按索引訪問,而是由 enumerate 函數(shù)自動(dòng)把每個(gè)元素變成 (index, element) 這樣的tuple,再迭代,就同時(shí)獲得了索引和元素本身。
迭代dict的value我們已經(jīng)了解了dict對(duì)象本身就是可迭代對(duì)象,用 for 循環(huán)直接迭代 dict,可以每次拿到dict的一個(gè)key。
如果我們希望迭代 dict 對(duì)象的value,應(yīng)該怎么做?
dict 對(duì)象有一個(gè) values 方法,這個(gè)方法把dict轉(zhuǎn)換成一個(gè)包含所有value的list,這樣,我們迭代的就是 dict的每一個(gè) value:
d = { 'Adam': 95, 'Lisa': 85, 'Bart': 59 }
print d.values
# [85, 95, 59]for vin d.values: print v# 85# 95# 59
如果仔細(xì)閱讀Python的文檔,還可以發(fā)現(xiàn),dict除了values方法外,還有一個(gè) itervalues 方法,用 itervalues 方法替代 values 方法,迭代效果完全一樣:
d = { 'Adam': 95, 'Lisa': 85, 'Bart': 59 }
print d.itervalues
print v
# 85
# 95
# 59
1. values 方法實(shí)際上把一個(gè) dict 轉(zhuǎn)換成了包含 value 的list。
2. 但是 itervalues 方法不會(huì)轉(zhuǎn)換,它會(huì)在迭代過程中依次從 dict 中取出 value,所以 itervalues 方法比 values 方法節(jié)省了生成 list 所需的內(nèi)存。
3. 打印 itervalues 發(fā)現(xiàn)它返回一個(gè)如果一個(gè)對(duì)象說自己可迭代,那我們就直接用 for 循環(huán)去迭代它,可見,迭代是一種抽象的數(shù)據(jù)操作,它不對(duì)迭代對(duì)象內(nèi)部的數(shù)據(jù)有任何要求。
迭代dict的key和value我們了解了如何迭代 dict 的key和value,那么,在一個(gè) for 循環(huán)中,能否同時(shí)迭代 key和value?答案是肯定的。
首先,我們看看 dict 對(duì)象的 items 方法返回的值:
>>> d = { 'Adam': 95, 'Lisa': 85, 'Bart': 59 }
>>> print d.items
[('Lisa', 85), ('Adam', 95), ('Bart', 59)]
可以看到,items 方法把dict對(duì)象轉(zhuǎn)換成了包含tuple的list,我們對(duì)這個(gè)list進(jìn)行迭代,可以同時(shí)獲得key和value:
>>> for key, value in d.items:
... print key, ':', value
...
Lisa : 85
Adam : 95
Bart : 59
和 values 有一個(gè) itervalues 類似, items 也有一個(gè)對(duì)應(yīng)的 iteritems,iteritems 不把dict轉(zhuǎn)換成list,而是在迭代過程中不斷給出 tuple,所以, iteritems 不占用額外的內(nèi)存。
生成列表要生成list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],我們可以用range(1, 11):
>>> range(1, 11)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
但如果要生成[1x1, 2x2, 3x3, ..., 10x10]怎么做?方法一是循環(huán):
>>> L =
>>> for x in range(1, 11):
... L.append(x * x)
...
>>> L
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
但是循環(huán)太繁瑣,而列表生成式則可以用一行語句代替循環(huán)生成上面的list:
>>> [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
這種寫法就是Python特有的列表生成式。利用列表生成式,可以以非常簡潔的代碼生成 list。
寫列表生成式時(shí),把要生成的元素 x * x 放到前面,后面跟 for 循環(huán),就可以把list創(chuàng)建出來,十分有用,多寫幾次,很快就可以熟悉這種語法。
復(fù)雜表達(dá)式使用for循環(huán)的迭代不僅可以迭代普通的list,還可以迭代dict。
完全可以通過一個(gè)復(fù)雜的列表生成式把它變成一個(gè) HTML 表格:
tds = ['%s%s'% (name, score)for name, scorein d.iteritems]
print '
print '
print '\n'.join(tds)
print '
'Name | Score |
---|---|
Lisa | 85 |
Adam | 95 |
Bart | 59 |
列表生成式的 for 循環(huán)后面還可以加上 if 判斷。例如:
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
如果我們只想要偶數(shù)的平方,不改動(dòng) range的情況下,可以加上 if 來篩選:
>>> [x * xfor xin range(1, 11)if x % 2 == 0]
[4, 16, 36, 64, 100]
有了 if 條件,只有 if 判斷為 True 的時(shí)候,才把循環(huán)的當(dāng)前元素添加到列表中。
多層表達(dá)式for循環(huán)可以嵌套,因此,在列表生成式中,也可以用多層 for 循環(huán)來生成列表。
對(duì)于字符串 'ABC' 和 '123',可以使用兩層循環(huán),生成全排列:
['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3']
翻譯成循環(huán)代碼就像下面這樣:
for nin '123':
L.append(m + n)
把函數(shù)作為參數(shù)一個(gè)簡單的高階函數(shù):
def add(x, y, f):
return f(x) + f(y)
如果傳入abs作為參數(shù)f的值:
add(-5, 9, abs)
根據(jù)函數(shù)的定義,函數(shù)執(zhí)行的代碼實(shí)際上是:
abs(-5) + abs(9)
由于參數(shù) x, y 和 f 都可以任意傳入,如果 f 傳入其他函數(shù),就可以得到不同的返回值。
map函數(shù)map是 Python 內(nèi)置的高階函數(shù),它接收一個(gè)函數(shù) f 和一個(gè) list,并通過把函數(shù) f 依次作用在 list 的每個(gè)元素上,得到一個(gè)新的 list 并返回。
例如,對(duì)于list [1, 2, 3, 4, 5, 6, 7, 8, 9]
如果希望把list的每個(gè)元素都作平方,就可以用map函數(shù):
因此,我們只需要傳入函數(shù)f(x)=x*x,就可以利用map函數(shù)完成這個(gè)計(jì)算:
def f(x):
return x*x
print map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
輸出結(jié)果:
[1, 4, 9, 10, 25, 36, 49, 64, 81]
注意:map函數(shù)不改變?cè)械?list,而是返回一個(gè)新的 list。
利用map函數(shù),可以把一個(gè) list 轉(zhuǎn)換為另一個(gè) list,只需要傳入轉(zhuǎn)換函數(shù)。
由于list包含的元素可以是任何類型,因此,map 不僅僅可以處理只包含數(shù)值的 list,事實(shí)上它可以處理包含任意類型的 list,只要傳入的函數(shù)f可以處理這種數(shù)據(jù)類型。
reduce函數(shù)reduce函數(shù)也是Python內(nèi)置的一個(gè)高階函數(shù)。reduce函數(shù)接收的參數(shù)和 map類似,一個(gè)函數(shù) f,一個(gè)list,但行為和 map不同,reduce傳入的函數(shù) f 必須接收兩個(gè)參數(shù),reduce對(duì)list的每個(gè)元素反復(fù)調(diào)用函數(shù)f,并返回最終結(jié)果值。
例如,編寫一個(gè)f函數(shù),接收x和y,返回x和y的和:
def f(x, y):
return x + y
調(diào)用 reduce(f, [1, 3, 5, 7, 9])時(shí),reduce函數(shù)將做如下計(jì)算:
先計(jì)算頭兩個(gè)元素:f(1, 3),結(jié)果為4;
再把結(jié)果和第3個(gè)元素計(jì)算:f(4, 5),結(jié)果為9;
再把結(jié)果和第4個(gè)元素計(jì)算:f(9, 7),結(jié)果為16;
再把結(jié)果和第5個(gè)元素計(jì)算:f(16, 9),結(jié)果為25;
由于沒有更多的元素了,計(jì)算結(jié)束,返回結(jié)果25。
上述計(jì)算實(shí)際上是對(duì) list 的所有元素求和。雖然Python內(nèi)置了求和函數(shù)sum,但是,利用reduce求和也很簡單。
reduce還可以接收第3個(gè)可選參數(shù),作為計(jì)算的初始值。如果把初始值設(shè)為100,計(jì)算:
reduce(f, [1, 3, 5, 7, 9], 100)
結(jié)果將變?yōu)?25,因?yàn)榈谝惠営?jì)算是:
計(jì)算初始值和第一個(gè)元素:f(100, 1),結(jié)果為101
filter函數(shù)filter函數(shù)是 Python 內(nèi)置的另一個(gè)有用的高階函數(shù),filter函數(shù)接收一個(gè)函數(shù) f 和一個(gè)list,這個(gè)函數(shù) f 的作用是對(duì)每個(gè)元素進(jìn)行判斷,返回 True或 False,filter根據(jù)判斷結(jié)果自動(dòng)過濾掉不符合條件的元素,返回由符合條件元素組成的新list。
例如,要從一個(gè)list [1, 4, 6, 7, 9, 12, 17]中刪除偶數(shù),保留奇數(shù),首先,要編寫一個(gè)判斷奇數(shù)的函數(shù):
def is_odd(x):
return x % 2 == 1
然后,利用filter過濾掉偶數(shù):
filter(is_odd, [1, 4, 6, 7, 9, 12, 17])
結(jié)果:[1, 7, 9, 17]
利用filter,可以完成很多有用的功能,例如,刪除 None 或者空字符串:
def is_not_empty(s):
return s and len(s.strip) > 0
filter(is_not_empty, ['test', None, '', 'str', ' ', 'END'])
結(jié)果:['test', 'str', 'END']
注意: s.strip(rm) 刪除 s 字符串中開頭、結(jié)尾處的 rm 序列的字符。
當(dāng)rm為空時(shí),默認(rèn)刪除空白符(包括'\n', '\r', '\t', ' '),如下:
a = ' 123'
a.strip
結(jié)果: '123'
a='\t\t123\r\n'
a.strip
結(jié)果:'123'
自定義排序函數(shù)
Python內(nèi)置的 sorted函數(shù)可對(duì)list進(jìn)行排序:
>>>sorted([36, 5, 12, 9, 21])
[5, 9, 12, 21, 36]但 sorted也是一個(gè)高階函數(shù),它可以接收一個(gè)比較函數(shù)來實(shí)現(xiàn)自定義排序,比較函數(shù)的定義是,傳入兩個(gè)待比較的元素 x, y,如果 x 應(yīng)該排在 y 的前面,返回 -1,如果 x 應(yīng)該排在 y 的后面,返回 1。如果 x 和 y 相等,返回 0。
因此,如果我們要實(shí)現(xiàn)倒序排序,只需要編寫一個(gè)reversed_cmp函數(shù):
def reversed_cmp(x, y):
if x > y:
return -1
if x <>
return 1
return 0
這樣,調(diào)用 sorted 并傳入 reversed_cmp 就可以實(shí)現(xiàn)倒序排序:
>>> sorted([36, 5, 12, 9, 21], reversed_cmp)
[36, 21, 12, 9, 5]
sorted也可以對(duì)字符串進(jìn)行排序,字符串默認(rèn)按照ASCII大小來比較:
>>> sorted(['bob', 'about', 'Zoo', 'Credit'])
['Credit', 'Zoo', 'about', 'bob']
'Zoo'排在'about'之前是因?yàn)?Z'的ASCII碼比'a'小。
返回函數(shù)Python的函數(shù)不但可以返回int、str、list、dict等數(shù)據(jù)類型,還可以返回函數(shù)!
例如,定義一個(gè)函數(shù) f,我們讓它返回一個(gè)函數(shù) g,可以這樣寫:
def f:
print 'call f...'
return g
仔細(xì)觀察上面的函數(shù)定義,我們?cè)诤瘮?shù) f 內(nèi)部又定義了一個(gè)函數(shù) g。由于函數(shù) g 也是一個(gè)對(duì)象,函數(shù)名 g 就是指向函數(shù) g 的變量,所以,最外層函數(shù) f 可以返回變量 g,也就是函數(shù) g 本身。
調(diào)用函數(shù) f,我們會(huì)得到 f 返回的一個(gè)函數(shù):
call f...
>>> x # 變量x是f返回的函數(shù):
>>> x # x指向函數(shù),因此可以調(diào)用
call g... # 調(diào)用x就是執(zhí)行g(shù)函數(shù)定義的代碼
請(qǐng)注意區(qū)分返回函數(shù)和返回值:
def myabs:
return abs # 返回函數(shù)
def myabs2(x):
return abs(x) # 返回函數(shù)調(diào)用的結(jié)果,返回值是一個(gè)數(shù)值
返回函數(shù)可以把一些計(jì)算延遲執(zhí)行。例如,如果定義一個(gè)普通的求和函數(shù):
def calc_sum(lst):
return sum(lst)
調(diào)用calc_sum函數(shù)時(shí),將立刻計(jì)算并得到結(jié)果:
>>> calc_sum([1, 2, 3, 4])
10
但是,如果返回一個(gè)函數(shù),就可以“延遲計(jì)算”:
def calc_sum(lst):
def lazy_sum:
return sum(lst)
return lazy_sum
# 調(diào)用calc_sum并沒有計(jì)算出結(jié)果,而是返回函數(shù):
>>> f = calc_sum([1, 2, 3, 4])
>>> f
# 對(duì)返回的函數(shù)進(jìn)行調(diào)用時(shí),才計(jì)算出結(jié)果:
>>> f
10
由于可以返回函數(shù),我們?cè)诤罄m(xù)代碼里就可以決定到底要不要調(diào)用該函數(shù)。
閉包在函數(shù)內(nèi)部定義的函數(shù)和外部定義的函數(shù)是一樣的,只是他們無法被外部訪問:
def g:
print 'g...'
def f:
print 'f...'
return g
將 g 的定義移入函數(shù) f 內(nèi)部,防止其他代碼調(diào)用 g:
def f:
print 'f...'
def g:
print 'g...'
return g
def calc_sum(lst):
def lazy_sum:
return sum(lst)
return lazy_sum
注意: 發(fā)現(xiàn)沒法把 lazy_sum 移到 calc_sum 的外部,因?yàn)樗昧?calc_sum 的參數(shù) lst。
像這種內(nèi)層函數(shù)引用了外層函數(shù)的變量(參數(shù)也算變量),然后返回內(nèi)層函數(shù)的情況,稱為閉包(Closure)。
閉包的特點(diǎn)是返回的函數(shù)還引用了外層函數(shù)的局部變量,所以,要正確使用閉包,就要確保引用的局部變量在函數(shù)返回后不能變。舉例如下:
# 希望一次返回3個(gè)函數(shù),分別計(jì)算1x1,2x2,3x3:
def count:
fs =
for i in range(1, 4):
def f:
return i*i
fs.append(f)
return fs
f1, f2, f3 = count
你可能認(rèn)為調(diào)用f1,f2和f3結(jié)果應(yīng)該是1,4,9,但實(shí)際結(jié)果全部都是 9(請(qǐng)自己動(dòng)手驗(yàn)證)。
原因就是當(dāng)count函數(shù)返回了3個(gè)函數(shù)時(shí),這3個(gè)函數(shù)所引用的變量 i 的值已經(jīng)變成了3。由于f1、f2、f3并沒有被調(diào)用,所以,此時(shí)他們并未計(jì)算 i*i,當(dāng) f1 被調(diào)用時(shí):
>>> f1
9 # 因?yàn)閒1現(xiàn)在才計(jì)算i*i,但現(xiàn)在i的值已經(jīng)變?yōu)?
因此,返回函數(shù)不要引用任何循環(huán)變量,或者后續(xù)會(huì)發(fā)生變化的變量。
匿名函數(shù)高階函數(shù)可以接收函數(shù)做參數(shù),有些時(shí)候,我們不需要顯式地定義函數(shù),直接傳入匿名函數(shù)更方便。
在Python中,對(duì)匿名函數(shù)提供了有限支持。還是以map函數(shù)為例,計(jì)算 f(x)=x2 時(shí),除了定義一個(gè)f(x)的函數(shù)外,還可以直接傳入匿名函數(shù):>>> map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])
[1, 4, 9, 16, 25, 36, 49, 64, 81]
通過對(duì)比可以看出,匿名函數(shù) lambda x: x * x 實(shí)際上就是:
def f(x):
return x * x
關(guān)鍵字lambda 表示匿名函數(shù),冒號(hào)前面的 x 表示函數(shù)參數(shù)。
匿名函數(shù)有個(gè)限制,就是只能有一個(gè)表達(dá)式,不寫return,返回值就是該表達(dá)式的結(jié)果。
使用匿名函數(shù),可以不必定義函數(shù)名,直接創(chuàng)建一個(gè)函數(shù)對(duì)象,很多時(shí)候可以簡化代碼:
>>> sorted([1, 3, 9, 5, 0], lambda x,y: -cmp(x,y))
[9, 5, 3, 1, 0]
返回函數(shù)的時(shí)候,也可以返回匿名函數(shù):
>>> myabs = lambda x: -x if x < 0="" else="">
>>> myabs(-1)
1
>>> myabs(1)
1
編寫無參數(shù)decoratorPython的 decorator 本質(zhì)上就是一個(gè)高階函數(shù),它接收一個(gè)函數(shù)作為參數(shù),然后,返回一個(gè)新函數(shù)。
使用 decorator 用Python提供的 @ 語法,這樣可以避免手動(dòng)編寫 f = decorate(f) 這樣的代碼。
考察一個(gè)@log的定義:
def log(f):
def fn(x):
print 'call ' + f.__name__ + '...'
return f(x)
return fn
對(duì)于階乘函數(shù),@log工作得很好:
@log
def factorial(n):
return reduce(lambda x,y: x*y, range(1, n+1))
print factorial(10)
結(jié)果:
call factorial...
3628800
但是,對(duì)于參數(shù)不是一個(gè)的函數(shù),調(diào)用將報(bào)錯(cuò):
@log
def add(x, y):
return x + y
print add(1, 2)
結(jié)果:
Traceback (most recent call last):
File 'test.py', line 15, in
print add(1,2)
TypeError: fn takes exactly 1 argument (2 given)
因?yàn)?add 函數(shù)需要傳入兩個(gè)參數(shù),但是 @log 寫死了只含一個(gè)參數(shù)的返回函數(shù)。
要讓 @log 自適應(yīng)任何參數(shù)定義的函數(shù),可以利用Python的 *args 和 **kw,保證任意個(gè)數(shù)的參數(shù)總是能正常調(diào)用:
def log(f):
def fn(*args, **kw):
print 'call ' + f.__name__ + '...'
return f(*args, **kw)
return fn
現(xiàn)在,對(duì)于任意函數(shù),@log 都能正常工作。
編寫帶參數(shù)decorator考察上一節(jié)的 @log 裝飾器:
def log(f):
def fn(x):
print 'call ' + f.__name__ + '...'
return f(x)
return fn
發(fā)現(xiàn)對(duì)于被裝飾的函數(shù),log打印的語句是不能變的(除了函數(shù)名)。
如果有的函數(shù)非常重要,希望打印出'[INFO] call xxx...',有的函數(shù)不太重要,希望打印出'[DEBUG] call xxx...',這時(shí),log函數(shù)本身就需要傳入'INFO'或'DEBUG'這樣的參數(shù),類似這樣:
@log('DEBUG')
def my_func:
pass
把上面的定義翻譯成高階函數(shù)的調(diào)用,就是:
my_func = log('DEBUG')(my_func)
上面的語句看上去還是比較繞,再展開一下:
log_decorator = log('DEBUG')
my_func = log_decorator(my_func)
上面的語句又相當(dāng)于:
@log_decorator
def my_func:
pass
所以,帶參數(shù)的log函數(shù)首先返回一個(gè)decorator函數(shù),再讓這個(gè)decorator函數(shù)接收my_func并返回新函數(shù):
def log(prefix):
def log_decorator(f):
def wrapper(*args, **kw):
print '[%s] %s...' % (prefix, f.__name__)
return f(*args, **kw)
return wrapper
return log_decorator
@log('DEBUG')
def test:
pass
print test
執(zhí)行結(jié)果:
[DEBUG] test...
None
對(duì)于這種3層嵌套的decorator定義,你可以先把它拆開:
# 標(biāo)準(zhǔn)decorator:
def log_decorator(f):
def wrapper(*args, **kw):
print '[%s] %s...' % (prefix, f.__name__)
return f(*args, **kw)
return wrapper
return log_decorator
# 返回decorator:
def log(prefix):
return log_decorator(f)
拆開以后會(huì)發(fā)現(xiàn),調(diào)用會(huì)失敗,因?yàn)樵?層嵌套的decorator定義中,最內(nèi)層的wrapper引用了最外層的參數(shù)prefix,所以,把一個(gè)閉包拆成普通的函數(shù)調(diào)用會(huì)比較困難。不支持閉包的編程語言要實(shí)現(xiàn)同樣的功能就需要更多的代碼。
完善decorator
@decorator可以動(dòng)態(tài)實(shí)現(xiàn)函數(shù)功能的增加,但是,經(jīng)過@decorator“改造”后的函數(shù),和原函數(shù)相比,除了功能多一點(diǎn)外,有沒有其它不同的地方?
在沒有decorator的情況下,打印函數(shù)名:
def f1(x):
pass
print f1.__name__
輸出: f1
有decorator的情況下,再打印函數(shù)名:
def log(f):
def wrapper(*args, **kw):
print 'call...'
return f(*args, **kw)
return wrapper
@log
def f2(x):
pass
print f2.__name__
輸出: wrapper
可見,由于decorator返回的新函數(shù)函數(shù)名已經(jīng)不是'f2',而是@log內(nèi)部定義的'wrapper'。這對(duì)于那些依賴函數(shù)名的代碼就會(huì)失效。decorator還改變了函數(shù)的__doc__等其它屬性。如果要讓調(diào)用者看不出一個(gè)函數(shù)經(jīng)過了@decorator的“改造”,就需要把原函數(shù)的一些屬性復(fù)制到新函數(shù)中:
def log(f):
def wrapper(*args, **kw):
print 'call...'
return f(*args, **kw)
wrapper.__name__ = f.__name__
wrapper.__doc__ = f.__doc__
return wrapper
這樣寫decorator很不方便,因?yàn)槲覀円埠茈y把原函數(shù)的所有必要屬性都一個(gè)一個(gè)復(fù)制到新函數(shù)上,所以Python內(nèi)置的functools可以用來自動(dòng)化完成這個(gè)“復(fù)制”的任務(wù):
import functools
def log(f):
@functools.wraps(f)
def wrapper(*args, **kw):
print 'call...'
return f(*args, **kw)
return wrapper
最后需要指出,由于我們把原函數(shù)簽名改成了(*args, **kw),因此,無法獲得原函數(shù)的原始參數(shù)信息。即便我們采用固定參數(shù)來裝飾只有一個(gè)參數(shù)的函數(shù):
def log(f):
@functools.wraps(f)
def wrapper(x):
print 'call...'
return f(x)
return wrapper
也可能改變?cè)瘮?shù)的參數(shù)名,因?yàn)樾潞瘮?shù)的參數(shù)名始終是 'x',原函數(shù)定義的參數(shù)名不一定叫 'x'。
關(guān)于Python裝飾器的講解有一篇比較通俗易懂的文章向大家推薦一下:
偏函數(shù)當(dāng)一個(gè)函數(shù)有很多參數(shù)時(shí),調(diào)用者就需要提供多個(gè)參數(shù)。如果減少參數(shù)個(gè)數(shù),就可以簡化調(diào)用者的負(fù)擔(dān)。
比如,int函數(shù)可以把字符串轉(zhuǎn)換為整數(shù),當(dāng)僅傳入字符串時(shí),int函數(shù)默認(rèn)按十進(jìn)制轉(zhuǎn)換:
>>> int('12345')
12345
但int函數(shù)還提供額外的base參數(shù),默認(rèn)值為10。如果傳入base參數(shù),就可以做 N 進(jìn)制的轉(zhuǎn)換:
>>> int('12345', base=8)
5349
>>> int('12345', 16)
74565
假設(shè)要轉(zhuǎn)換大量的二進(jìn)制字符串,每次都傳入int(x, base=2)非常麻煩,于是,我們想到,可以定義一個(gè)int2的函數(shù),默認(rèn)把base=2傳進(jìn)去:
def int2(x, base=2):
return int(x, base)
這樣,我們轉(zhuǎn)換二進(jìn)制就非常方便了:
>>> int2('1000000')
64
>>> int2('1010101')
85
functools.partial就是幫助我們創(chuàng)建一個(gè)偏函數(shù)的,不需要我們自己定義int2,可以直接使用下面的代碼創(chuàng)建一個(gè)新的函數(shù)int2:
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85
所以,functools.partial可以把一個(gè)參數(shù)多的函數(shù)變成一個(gè)參數(shù)少的新函數(shù),少的參數(shù)需要在創(chuàng)建時(shí)指定默認(rèn)值,這樣,新函數(shù)調(diào)用的難度就降低了。
模塊和包當(dāng)代碼數(shù)量逐漸增多時(shí),不可能全部都放在一個(gè).py文件中,這樣對(duì)以后的修改和查找都會(huì)帶來很大的困難,所以就會(huì)將不同功能的代碼抽離出來做成不同的模塊。然后也解決了在同一個(gè).py文件中出現(xiàn)大量的變量重名問題。不過當(dāng)模塊出現(xiàn)重名時(shí)就可以用到包了。包,也就是一個(gè)存放著.p文件即模塊的文件夾。
模塊的使用:
模塊:main.py
引用其它模塊,
# main.py
import math
Print math.pow(2.12)
引用math模塊時(shí)先寫import math
包是文件夾,模塊時(shí)xxx.py文件,包可以多級(jí)。
那么如何區(qū)分包和普通文件呢,在包內(nèi)一定又有個(gè)_init_.py文件,這個(gè)是必須有的。
導(dǎo)入模塊要使用一個(gè)模塊,我們必須首先導(dǎo)入該模塊。Python使用import語句導(dǎo)入一個(gè)模塊。例如,導(dǎo)入系統(tǒng)自帶的模塊 math:
import math
你可以認(rèn)為math就是一個(gè)指向已導(dǎo)入模塊的變量,通過該變量,我們可以訪問math模塊中所定義的所有公開的函數(shù)、變量和類:
>>> math.pow(2, 0.5) # pow是函數(shù)
1.4142135623730951
>>> math.pi # pi是變量
3.141592653589793
如果我們只希望導(dǎo)入用到的math模塊的某幾個(gè)函數(shù),而不是所有函數(shù),可以用下面的語句:
from math import pow, sin, log
這樣,可以直接引用 pow, sin, log 這3個(gè)函數(shù),但math的其他函數(shù)沒有導(dǎo)入進(jìn)來:
>>> pow(2, 10)
1024.0
>>> sin(3.14)
0.0015926529164868282
如果遇到名字沖突怎么辦?比如math模塊有一個(gè)log函數(shù),logging模塊也有一個(gè)log函數(shù),如果同時(shí)使用,如何解決名字沖突?
如果使用import導(dǎo)入模塊名,由于必須通過模塊名引用函數(shù)名,因此不存在沖突:
import math, logging
print math.log(10) # 調(diào)用的是math的log函數(shù)
logging.log(10, 'something') # 調(diào)用的是logging的log函數(shù)
如果使用 from...import 導(dǎo)入 log 函數(shù),勢必引起沖突。這時(shí),可以給函數(shù)起個(gè)“別名”來避免沖突:
from math import log
from logging import log as logger # logging的log現(xiàn)在變成了logger
print log(10) # 調(diào)用的是math的log
logger(10, 'import from logging') # 調(diào)用的是logging的log
這里可以和C++ 中的繼承中出現(xiàn)變量名字相同時(shí)的處理方法相比較。
File 'ImportError: No module named something
有的時(shí)候,兩個(gè)不同的模塊提供了相同的功能,比如 StringIO 和 cStringIO 都提供了StringIO這個(gè)功能。
這是因?yàn)镻ython是動(dòng)態(tài)語言,解釋執(zhí)行,因此Python代碼運(yùn)行速度慢。
如果要提高Python代碼的運(yùn)行速度,最簡單的方法是把某些關(guān)鍵函數(shù)用 C 語言重寫,這樣就能大大提高執(zhí)行速度。
同樣的功能,StringIO 是純Python代碼編寫的,而 cStringIO 部分函數(shù)是 C 寫的,因此 cStringIO 運(yùn)行速度更快。
利用ImportError錯(cuò)誤,我們經(jīng)常在Python中動(dòng)態(tài)導(dǎo)入模塊:
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
上述代碼先嘗試從cStringIO導(dǎo)入,如果失敗了(比如cStringIO沒有被安裝),再嘗試從StringIO導(dǎo)入。這樣,如果cStringIO模塊存在,則我們將獲得更快的運(yùn)行速度,如果cStringIO不存在,則頂多代碼運(yùn)行速度會(huì)變慢,但不會(huì)影響代碼的正常執(zhí)行。
try 的作用是捕獲錯(cuò)誤,并在捕獲到指定錯(cuò)誤時(shí)執(zhí)行 except 語句。
使用__future__Python的新版本會(huì)引入新的功能,但是,實(shí)際上這些功能在上一個(gè)老版本中就已經(jīng)存在了。要“試用”某一新的特性,就可以通過導(dǎo)入__future__模塊的某些功能來實(shí)現(xiàn)。
例如,Python 2.7的整數(shù)除法運(yùn)算結(jié)果仍是整數(shù):
>>> 10 / 3
3
但是,Python 3.x已經(jīng)改進(jìn)了整數(shù)的除法運(yùn)算,“/”除將得到浮點(diǎn)數(shù),“//”除才仍是整數(shù):
>>> 10 / 3
3.3333333333333335
>>> 10 // 3
3
要在Python 2.7中引入3.x的除法規(guī)則,導(dǎo)入__future__的division:
>>> from __future__ import division
>>> print 10 / 3
3.3333333333333335
當(dāng)新版本的一個(gè)特性與舊版本不兼容時(shí),該特性將會(huì)在舊版本中添加到__future__中,以便舊的代碼能在舊版本中測試新特性。
定義類并創(chuàng)建實(shí)例在Python中,類通過 class 關(guān)鍵字定義。以 Person 為例,定義一個(gè)Person類如下:
class Person(object):
pass
按照 Python 的編程習(xí)慣,類名以大寫字母開頭,緊接著是(object),表示該類是從哪個(gè)類繼承下來的。類的繼承將在后面的章節(jié)講解,現(xiàn)在我們只需要簡單地從object類繼承。
有了Person類的定義,就可以創(chuàng)建出具體的xiaoming、xiaohong等實(shí)例。創(chuàng)建實(shí)例使用 類名+,類似函數(shù)調(diào)用的形式創(chuàng)建:
xiaoming = Person
xiaohong = Person
創(chuàng)建實(shí)例屬性雖然可以通過Person類創(chuàng)建出xiaoming、xiaohong等實(shí)例,但是這些實(shí)例看上除了地址不同外,沒有什么其他不同。在現(xiàn)實(shí)世界中,區(qū)分xiaoming、xiaohong要依靠他們各自的名字、性別、生日等屬性。
如何讓每個(gè)實(shí)例擁有各自不同的屬性?由于Python是動(dòng)態(tài)語言,對(duì)每一個(gè)實(shí)例,都可以直接給他們的屬性賦值,例如,給xiaoming這個(gè)實(shí)例加上name、gender和birth屬性:
xiaoming = Person
xiaoming.name = 'Xiao Ming'
xiaoming.gender = 'Male'
xiaoming.birth = '1990-1-1'
xiaohong = Person
xiaohong.name = 'Xiao Hong'
xiaohong.school = 'No. 1 High School'
xiaohong.grade = 2
實(shí)例的屬性可以像普通變量一樣進(jìn)行操作:
xiaohong.grade = xiaohong.grade + 1
例子:
class Person(object):
pass
p1 = Person
p1.name = 'Bart'
p2 = Person
p2.name = 'Adam'
p3 = Person
p3.name = 'Lisa'
L1 = [p1, p2, p3]
L2 = sorted(L1,lambda p1,p2:cmp(p1.name,p2.name))
print L2[0].name
print L2[1].name
print L2[2].name
運(yùn)行結(jié)果:
Adam
Bart
Lisa
初始化實(shí)例屬性雖然我們可以自由地給一個(gè)實(shí)例綁定各種屬性,但是,現(xiàn)實(shí)世界中,一種類型的實(shí)例應(yīng)該擁有相同名字的屬性。例如,Person類應(yīng)該在創(chuàng)建的時(shí)候就擁有 name、gender 和 birth 屬性,怎么辦?
在定義 Person 類時(shí),可以為Person類添加一個(gè)特殊的__init__方法,當(dāng)創(chuàng)建實(shí)例時(shí),__init__方法被自動(dòng)調(diào)用,我們就能在此為每個(gè)實(shí)例都統(tǒng)一加上以下屬性:
class Person(object):
def __init__(self, name, gender, birth):
self.name = name
self.gender = gender
self.birth = birth
__init__ 方法的第一個(gè)參數(shù)必須是 self(也可以用別的名字,但建議使用習(xí)慣用法),后續(xù)參數(shù)則可以自由指定,和定義函數(shù)沒有任何區(qū)別。
相應(yīng)地,創(chuàng)建實(shí)例時(shí),就必須要提供除 self 以外的參數(shù):
xiaoming = Person('Xiao Ming', 'Male', '1991-1-1')
xiaohong = Person('Xiao Hong', 'Female', '1992-2-2')
有了__init__方法,每個(gè)Person實(shí)例在創(chuàng)建時(shí),都會(huì)有 name、gender 和 birth 這3個(gè)屬性,并且,被賦予不同的屬性值,訪問屬性使用.操作符:
print xiaoming.name# 輸出 'Xiao Ming'
print xiaohong.birth# 輸出 '1992-2-2'
>>> class Person(object):
... def __init__(name, gender, birth):
... pass
...
>>> xiaoming = Person('Xiao Ming', 'Male', '1990-1-1')
File 'TypeError: __init__ takes exactly 3 arguments (4 given)
這會(huì)導(dǎo)致創(chuàng)建失敗或運(yùn)行不正常,因?yàn)榈谝粋€(gè)參數(shù)name被Python解釋器傳入了實(shí)例的引用,從而導(dǎo)致整個(gè)方法的調(diào)用參數(shù)位置全部沒有對(duì)上。
注:_init_與C++中的構(gòu)造函數(shù)作用類似,初始化
訪問限制我們可以給一個(gè)實(shí)例綁定很多屬性,如果有些屬性不希望被外部訪問到怎么辦?
Python對(duì)屬性權(quán)限的控制是通過屬性名來實(shí)現(xiàn)的,如果一個(gè)屬性由雙下劃線開頭(__),該屬性就無法被外部訪問??蠢樱?/span>
self.__job = 'Student'
File 'AttributeError: 'Person' object has no attribute '__job'
可見,只有以雙下劃線開頭的'__job'不能直接被外部訪問。
但是,如果一個(gè)屬性以'__xxx__'的形式定義,那它又可以被外部訪問了,以'__xxx__'定義的屬性在Python的類中被稱為特殊屬性,有很多預(yù)定義的特殊屬性可以使用,通常我們不要把普通屬性用'__xxx__'定義。
以單下劃線開頭的屬性'_xxx'雖然也可以被外部訪問,但是,按照習(xí)慣,他們不應(yīng)該被外部訪問。
創(chuàng)建類屬性類是模板,而實(shí)例則是根據(jù)類創(chuàng)建的對(duì)象。
綁定在一個(gè)實(shí)例上的屬性不會(huì)影響其他實(shí)例,但是,類本身也是一個(gè)對(duì)象,如果在類上綁定一個(gè)屬性,則所有實(shí)例都可以訪問類的屬性,并且,所有實(shí)例訪問的類屬性都是同一個(gè)!也就是說,實(shí)例屬性每個(gè)實(shí)例各自擁有,互相獨(dú)立,而類屬性有且只有一份。
def __init__(self, name):
self.name = name
因?yàn)轭悓傩允侵苯咏壎ㄔ陬惿系?,所以,訪問類屬性不需要?jiǎng)?chuàng)建實(shí)例,就可以直接訪問:
print Person.address# => Earth
對(duì)一個(gè)實(shí)例調(diào)用類的屬性也是可以訪問的,所有實(shí)例都可以訪問到它所屬的類的屬性:
p1 = Person('Bob')
p2 = Person('Alice')
print p1.address# => Earth
print p2.address# => Earth
由于Python是動(dòng)態(tài)語言,類屬性也是可以動(dòng)態(tài)添加和修改的:
Person.address = 'China'
print p1.address# => 'China'
print p2.address# => 'China'
因?yàn)轭悓傩灾挥幸环?,所以,?dāng)Person類的address改變時(shí),所有實(shí)例訪問到的類屬性都改變了。
類屬性和實(shí)例屬性名字沖突怎么辦修改類屬性會(huì)導(dǎo)致所有實(shí)例訪問到的類屬性全部都受影響,但是,如果在實(shí)例變量上修改類屬性會(huì)發(fā)生什么問題呢?
class Person(object):
address = 'Earth'
def __init__(self, name):
self.name = name
p1 = Person('Bob')
p2 = Person('Alice')
print 'Person.address = ' + Person.address
p1.address = 'China'
print 'p1.address = ' + p1.address
print 'p2.address = ' + p2.address
結(jié)果如下:
Person.address = Earth
p1.address = China
p2.address = Earth
我們發(fā)現(xiàn),在設(shè)置了 p1.address = 'China' 后,p1訪問 address 確實(shí)變成了 'China',但是,Person.address和p2.address仍然是'Earch',怎么回事?
原因是 p1.address = 'China'并沒有改變 Person 的 address,而是給 p1這個(gè)實(shí)例綁定了實(shí)例屬性address ,對(duì)p1來說,它有一個(gè)實(shí)例屬性address(值是'China'),而它所屬的類Person也有一個(gè)類屬性address,所以:
訪問 p1.address 時(shí),優(yōu)先查找實(shí)例屬性,返回'China'。
訪問 p2.address 時(shí),p2沒有實(shí)例屬性address,但是有類屬性address,因此返回'Earth'。
可見,當(dāng)實(shí)例屬性和類屬性重名時(shí),實(shí)例屬性優(yōu)先級(jí)高,它將屏蔽掉對(duì)類屬性的訪問。
當(dāng)我們把 p1 的 address 實(shí)例屬性刪除后,訪問 p1.address 就又返回類屬性的值 'Earth'了:
del p1.address
print p1.address# => Earth
可見,千萬不要在實(shí)例上修改類屬性,它實(shí)際上并沒有修改類屬性,而是給實(shí)例綁定了一個(gè)實(shí)例屬性。
定義實(shí)例方法一個(gè)實(shí)例的私有屬性就是以__開頭的屬性,無法被外部訪問,那這些屬性定義有什么用?
雖然私有屬性無法從外部訪問,但是,從類的內(nèi)部是可以訪問的。除了可以定義實(shí)例的屬性外,還可以定義實(shí)例的方法。
實(shí)例的方法就是在類中定義的函數(shù),它的第一個(gè)參數(shù)永遠(yuǎn)是 self,指向調(diào)用該方法的實(shí)例本身,其他參數(shù)和一個(gè)普通函數(shù)是完全一樣的:
class Person(object):
def __init__(self, name):
self.__name = name
def get_name(self):
return self.__name
get_name(self) 就是一個(gè)實(shí)例方法,它的第一個(gè)參數(shù)是self。__init__(self, name)其實(shí)也可看做是一個(gè)特殊的實(shí)例方法。
調(diào)用實(shí)例方法必須在實(shí)例上調(diào)用:
p1 = Person('Bob')
print p1.get_name # self不需要顯式傳入# => Bob
在實(shí)例方法內(nèi)部,可以訪問所有實(shí)例屬性,這樣,如果外部需要訪問私有屬性,可以通過方法調(diào)用獲得,這種數(shù)據(jù)封裝的形式除了能保護(hù)內(nèi)部數(shù)據(jù)一致性外,還可以簡化外部調(diào)用的難度。
方法也是屬性我們?cè)?/span> class 中定義的實(shí)例方法其實(shí)也是屬性,它實(shí)際上是一個(gè)函數(shù)對(duì)象:
class Person(object):
def __init__(self, name, score):
self.name = name
self.score = score
def get_grade(self):
return 'A'
p1 = Person('Bob', 90)
print p1.get_grade# => A也就是說,p1.get_grade 返回的是一個(gè)函數(shù)對(duì)象,但這個(gè)函數(shù)是一個(gè)綁定到實(shí)例的函數(shù),p1.get_grade 才是方法調(diào)用。
因?yàn)榉椒ㄒ彩且粋€(gè)屬性,所以,它也可以動(dòng)態(tài)地添加到實(shí)例上,只是需要用 types.MethodType 把一個(gè)函數(shù)變?yōu)橐粋€(gè)方法:
import types
def fn_get_grade(self):
if self.score >= 80:
return 'A'
if self.score >= 60:
return 'B'
return 'C'
class Person(object):
def __init__(self, name, score):
self.name = name
self.score = score
p1 = Person('Bob', 90)p1.get_grade = types.MethodType(fn_get_grade, p1, Person)
print p1.get_grade# => A
p2 = Person('Alice', 65)
print p2.get_grade# ERROR: AttributeError: 'Person' object has no attribute 'get_grade'
# 因?yàn)閜2實(shí)例并沒有綁定get_grade
給一個(gè)實(shí)例動(dòng)態(tài)添加方法并不常見,直接在class中定義要更直觀。
定義類方法和屬性類似,方法也分實(shí)例方法和類方法。
在class中定義的全部是實(shí)例方法,實(shí)例方法第一個(gè)參數(shù) self 是實(shí)例本身。
要在class中定義類方法,需要這么寫:
class Person(object):
count = 0
@classmethod
def how_many(cls):
return cls.count
def __init__(self, name):
self.name = name
Person.count = Person.count + 1
print Person.how_many
p1 = Person('Bob')
通過標(biāo)記一個(gè) @classmethod,該方法將綁定到 Person 類上,而非類的實(shí)例。類方法的第一個(gè)參數(shù)將傳入類本身,通常將參數(shù)名命名為 cls,上面的 cls.count 實(shí)際上相當(dāng)于 Person.count。
因?yàn)槭窃陬惿险{(diào)用,而非實(shí)例上調(diào)用,因此類方法無法獲得任何實(shí)例變量,只能獲得類的引用。
類的繼承
涉及到的概念:父類(基類、超類),子類(派生類、繼承類)。
類A繼承類B后,B成為父類,A則是子類。那么B中的一些實(shí)例屬性A也同樣擁有。這樣就會(huì)避免輸入
許多重復(fù)的的代碼。如果沒有類能夠繼承,那么就按標(biāo)準(zhǔn)繼承object。
繼承一個(gè)類如果已經(jīng)定義了Person類,需要定義新的Student和Teacher類時(shí),可以直接從Person類繼承:
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
class Student(Person):
def __init__(self, name, gender, score):
super(Student, self).__init__(name, gender)
self.score = score
一定要用 super(Student, self).__init__(name, gender) 去初始化父類,否則,繼承自 Person 的 Student 將沒有 name 和 gender。
函數(shù)super(Student, self)將返回當(dāng)前類繼承的父類,即 Person ,然后調(diào)用__init__方法,注意self參數(shù)已在super中傳入,在__init__中將隱式傳遞,不需要寫出(也不能寫)。
判斷類型函數(shù)isinstance可以判斷一個(gè)變量的類型,既可以用在Python內(nèi)置的數(shù)據(jù)類型如str、list、dict,也可以用在我們自定義的類,它們本質(zhì)上都是數(shù)據(jù)類型。
假設(shè)有如下的 Person、Student 和 Teacher 的定義及繼承關(guān)系如下:
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
self.score = score
class Teacher(Person):
def __init__(self, name, gender, course):
super(Teacher, self).__init__(name, gender)
self.course = course
p = Person('Tim', 'Male')
s = Student('Bob', 'Male', 88)
t = Teacher('Alice', 'Female', 'English')
當(dāng)我們拿到變量 p、s、t 時(shí),可以使用 isinstance 判斷類型:
>>> isinstance(p, Person)
True # p是Person類型
>>> isinstance(p, Student)
False # p不是Student類型
>>> isinstance(p, Teacher)
False # p不是Teacher類型
這說明在繼承鏈上,一個(gè)父類的實(shí)例不能是子類類型,因?yàn)樽宇惐雀割惗嗔艘恍傩院头椒ā?/span>
我們?cè)倏疾?/span> s :
>>> isinstance(s, Person)
True # s是Person類型
>>> isinstance(s, Student)
True # s是Student類型
>>> isinstance(s, Teacher)
False # s不是Teacher類型
s 是Student類型,不是Teacher類型,這很容易理解。但是,s 也是Person類型,因?yàn)镾tudent繼承自Person,雖然它比Person多了一些屬性和方法,但是,把 s 看成Person的實(shí)例也是可以的。
這說明在一條繼承鏈上,一個(gè)實(shí)例可以看成它本身的類型,也可以看成它父類的類型。
多態(tài)
類具有繼承關(guān)系,并且子類類型可以向上轉(zhuǎn)型看做父類類型,如果我們從 Person 派生出 Student和Teacher ,并都寫了一個(gè) whoAmI 方法:
def whoAmI(self):
return 'I am a Person, my name is %s' % self.name
class Student(Person):
def __init__(self, name, gender, score):
super(Student, self).__init__(name, gender)
self.score = score
def whoAmI(self):
return 'I am a Student, my name is %s' % self.name
class Teacher(Person):
def __init__(self, name, gender, course):
super(Teacher, self).__init__(name, gender)
self.course = course
return 'I am a Teacher, my name is %s' % self.name
在一個(gè)函數(shù)中,如果我們接收一個(gè)變量 x,則無論該 x 是 Person、Student還是 Teacher,都可以正確打印出結(jié)果:
def who_am_i(x):
print x.whoAmI
p = Person('Tim', 'Male')
s = Student('Bob', 'Male', 88)
t = Teacher('Alice', 'Female', 'English')
who_am_i(p)
who_am_i(s)
who_am_i(t)
運(yùn)行結(jié)果:
I am a Person, my name is Tim
I am a Student, my name is Bob
I am a Teacher, my name is Alice
這種行為稱為多態(tài)。也就是說,方法調(diào)用將作用在 x 的實(shí)際類型上。s 是Student類型,它實(shí)際上擁有自己的 whoAmI方法以及從 Person繼承的 whoAmI方法,但調(diào)用 s.whoAmI總是先查找它自身的定義,如果沒有定義,則順著繼承鏈向上查找,直到在某個(gè)父類中找到為止。
由于Python是動(dòng)態(tài)語言,所以,傳遞給函數(shù) who_am_i(x)的參數(shù) x不一定是 Person 或 Person 的子類型。任何數(shù)據(jù)類型的實(shí)例都可以,只要它有一個(gè)whoAmI的方法即可:
class Book(object):
def whoAmI(self):
return 'I am a book'
這是動(dòng)態(tài)語言和靜態(tài)語言(例如Java)最大的差別之一。動(dòng)態(tài)語言調(diào)用實(shí)例方法,不檢查類型,只要方法存在,參數(shù)正確,就可以調(diào)用。
多重繼承除了從一個(gè)父類繼承外,Python允許從多個(gè)父類繼承,稱為多重繼承。
多重繼承的繼承鏈就不是一棵樹了,它像這樣:
class A(object):
def __init__(self, a):
print 'init A...'
self.a = a
class B(A):
def __init__(self, a):
super(B, self).__init__(a)
print 'init B...'
class C(A):
def __init__(self, a):
super(C, self).__init__(a)
print 'init C...'
def __init__(self, a):
super(D, self).__init__(a)
print 'init D...'
看下圖:
像這樣,D 同時(shí)繼承自 B 和 C,也就是 D 擁有了 A、B、C 的全部功能。多重繼承通過 super調(diào)用__init__方法時(shí),A 雖然被繼承了兩次,但__init__只調(diào)用一次:
>>> d = D('d')
init A...
init C...
init B...
init D...
多重繼承的目的是從兩種繼承樹中分別選擇并繼承出子類,以便組合功能使用。
舉個(gè)例子,Python的網(wǎng)絡(luò)服務(wù)器有TCPServer、UDPServer、UnixStreamServer、UnixDatagramServer,而服務(wù)器運(yùn)行模式有 多進(jìn)程ForkingMixin 和 多線程ThreadingMixin兩種。
要?jiǎng)?chuàng)建多進(jìn)程模式的 TCPServer:
class MyTCPServer(TCPServer, ForkingMixin)
pass
要?jiǎng)?chuàng)建多線程模式的 UDPServer:
class MyUDPServer(UDPServer, ThreadingMixin):
pass
如果沒有多重繼承,要實(shí)現(xiàn)上述所有可能的組合需要 4x2=8 個(gè)子類。
獲取對(duì)象信息拿到一個(gè)變量,除了用 isinstance 判斷它是否是某種類型的實(shí)例外,還有沒有別的方法獲取到更多的信息呢?
例如,已有定義:
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def __init__(self, name, gender, score):
super(Student, self).__init__(name, gender)
self.score = score
def whoAmI(self):
return 'I am a Student, my name is %s' % self.name
首先可以用 type 函數(shù)獲取變量的類型,它返回一個(gè) Type 對(duì)象:
>>> type(123)
>>> s = Student('Bob', 'Male', 88)
>>> type(s)
其次,可以用 dir 函數(shù)獲取變量的所有屬性:
>>> dir(123) # 整數(shù)也有很多屬性...
['__abs__', '__add__', '__and__', '__class__', '__cmp__', ...]
>>> dir(s)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'gender', 'name', 'score', 'whoAmI']
對(duì)于實(shí)例變量,dir返回所有實(shí)例屬性,包括`__class__`這類有特殊意義的屬性。注意到方法`whoAmI`也是 s 的一個(gè)屬性。
dir返回的屬性是字符串列表,如果已知一個(gè)屬性名稱,要獲取或者設(shè)置對(duì)象的屬性,就需要用 getattr 和 setattr函數(shù)了:
>>> getattr(s, 'name') # 獲取name屬性
'Bob'
>>> setattr(s, 'name', 'Adam') # 設(shè)置新的name屬性
File 'AttributeError: 'Student' object has no attribute 'age'
>>> getattr(s, 'age', 20) # 獲取age屬性,如果屬性不存在,就返回默認(rèn)值20:
20
特殊方法(魔術(shù)方法)__str__和__repr__如果要把一個(gè)類的實(shí)例變成 str,就需要實(shí)現(xiàn)特殊方法__str__:
def __init__(self, name, gender):
self.name = name
self.gender = gender
def __str__(self):
return '(Person: %s, %s)' % (self.name, self.gender)
現(xiàn)在,在交互式命令行下用 print 試試:
>>> p = Person('Bob', 'male')
>>> print p
(Person: Bob, male)
但是,如果直接敲變量 p:
>>> p
似乎__str__ 不會(huì)被調(diào)用。
因?yàn)?Python 定義了__str__和__repr__兩種方法,__str__用于顯示給用戶,而__repr__用于顯示給開發(fā)人員。
有一個(gè)偷懶的定義__repr__的方法:
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def __str__(self):
return '(Person: %s, %s)' % (self.name, self.gender)
__repr__ = __str__
__cmp__對(duì) int、str 等內(nèi)置數(shù)據(jù)類型排序時(shí),Python的 sorted 按照默認(rèn)的比較函數(shù) cmp 排序,但是,如果對(duì)一組 Student 類的實(shí)例排序時(shí),就必須提供我們自己的特殊方法 __cmp__:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def __str__(self):
return '(%s: %s)' % (self.name, self.score)
__repr__ = __str__
def __cmp__(self, s):
if self.name <>
return -1
elif self.name > s.name:
return 1
else:
return 0
上述 Student 類實(shí)現(xiàn)了__cmp__方法,__cmp__用實(shí)例自身self和傳入的實(shí)例 s 進(jìn)行比較,如果 self 應(yīng)該排在前面,就返回 -1,如果 s 應(yīng)該排在前面,就返回1,如果兩者相當(dāng),返回 0。
Student類實(shí)現(xiàn)了按name進(jìn)行排序:
>>> L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 77)]
>>> print sorted(L)
[(Alice: 77), (Bob: 88), (Tim: 99)]
注意: 如果list不僅僅包含 Student 類,則 __cmp__ 可能會(huì)報(bào)錯(cuò):
L = [Student('Tim', 99), Student('Bob', 88), 100, 'Hello']
print sorted(L)
__len__如果一個(gè)類表現(xiàn)得像一個(gè)list,要獲取有多少個(gè)元素,就得用 len 函數(shù)。
要讓 len 函數(shù)工作正常,類必須提供一個(gè)特殊方法__len__,它返回元素的個(gè)數(shù)。
例如,我們寫一個(gè) Students 類,把名字傳進(jìn)去:
class Students(object):
def __init__(self, *args):
self.names = args
def __len__(self):
return len(self.names)
只要正確實(shí)現(xiàn)了__len__方法,就可以用len函數(shù)返回Students實(shí)例的“長度”:
>>> ss = Students('Bob', 'Alice', 'Tim')
>>> print len(ss)
3
數(shù)學(xué)運(yùn)算Python 提供的基本數(shù)據(jù)類型 int、float 可以做整數(shù)和浮點(diǎn)的四則運(yùn)算以及乘方等運(yùn)算。
但是,四則運(yùn)算不局限于int和float,還可以是有理數(shù)、矩陣等。
要表示有理數(shù),可以用一個(gè)Rational類來表示:
class Rational(object):
def __init__(self, p, q):
self.p = p
self.q = q
p、q 都是整數(shù),表示有理數(shù) p/q。
如果要讓Rational進(jìn)行+運(yùn)算,需要正確實(shí)現(xiàn)__add__:
class Rational(object):
def __init__(self, p, q):
self.p = p
self.q = q
def __add__(self, r):
return Rational(self.p * r.q + self.q * r.p, self.q * r.q)
def __str__(self):
return '%s/%s' % (self.p, self.q)
__repr__ = __str__
現(xiàn)在可以試試有理數(shù)加法:
>>> r1 = Rational(1, 3)
>>> r2 = Rational(1, 2)
>>> print r1 + r2
5/6
類型轉(zhuǎn)換Rational類實(shí)現(xiàn)了有理數(shù)運(yùn)算,但是,如果要把結(jié)果轉(zhuǎn)為 int 或 float 怎么辦?
考察整數(shù)和浮點(diǎn)數(shù)的轉(zhuǎn)換:
>>> int(12.34)
12
>>> float(12)
12.0
如果要把 Rational 轉(zhuǎn)為 int,應(yīng)該使用:
r = Rational(12, 5)
n = int(r)
要讓int函數(shù)正常工作,只需要實(shí)現(xiàn)特殊方法__int__:
class Rational(object):
def __init__(self, p, q):
self.p = p
self.q = q
def __int__(self):
return self.p // self.q
結(jié)果如下:
>>> print int(Rational(7, 2))
3
>>> print int(Rational(1, 3))
0
同理,要讓float函數(shù)正常工作,只需要實(shí)現(xiàn)特殊方法__float__。
@property考察 Student 類:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
當(dāng)我們想要修改一個(gè) Student 的 scroe 屬性時(shí),可以這么寫:
s = Student('Bob', 59)
s.score = 60
但是也可以這么寫:
s.score = 1000
顯然,直接給屬性賦值無法檢查分?jǐn)?shù)的有效性。
如果利用兩個(gè)方法:
class Student(object):
def __init__(self, name, score):
self.name = name
self.__score = score
def get_score(self):
return self.__score
def set_score(self, score):
if score < 0="" or="" score=""> 100:
raise ValueError('invalid score')
這樣一來,s.set_score(1000) 就會(huì)報(bào)錯(cuò)。
這種使用 get/set 方法來封裝對(duì)一個(gè)屬性的訪問在許多面向?qū)ο缶幊痰恼Z言中都很常見。
但是寫 s.get_score 和 s.set_score 沒有直接寫 s.score 來得直接。
有沒有兩全其美的方法?----有。
因?yàn)镻ython支持高階函數(shù),在函數(shù)式編程中我們介紹了裝飾器函數(shù),可以用裝飾器函數(shù)把 get/set 方法“裝飾”成屬性調(diào)用:
class Student(object):
def __init__(self, name, score):
self.name = name
self.__score = score
@property
def score(self):
return self.__score
@score.setter
def score(self, score):
if score < 0="" or="" score=""> 100:
raise ValueError('invalid score')
注意: 第一個(gè)score(self)是get方法,用@property裝飾,第二個(gè)score(self, score)是set方法,用@score.setter裝飾,@score.setter是前一個(gè)@property裝飾后的副產(chǎn)品。
現(xiàn)在,就可以像使用屬性一樣設(shè)置score了:
>>> s = Student('Bob', 59)
>>> s.score = 60
>>> print s.score
60
>>> s.score = 1000
Traceback (most recent call last):
...
ValueError: invalid score
說明對(duì) score 賦值實(shí)際調(diào)用的是 set方法。
__slots__由于Python是動(dòng)態(tài)語言,任何實(shí)例在運(yùn)行期都可以動(dòng)態(tài)地添加屬性。
如果要限制添加的屬性,例如,Student類只允許添加 name、gender和score 這3個(gè)屬性,就可以利用Python的一個(gè)特殊的__slots__來實(shí)現(xiàn)。
顧名思義,__slots__是指一個(gè)類允許的屬性列表:
class Student(object):
__slots__ = ('name', 'gender', 'score')
def __init__(self, name, gender, score):
self.name = name
self.gender = gender
self.score = score
現(xiàn)在,對(duì)實(shí)例進(jìn)行操作:
>>> s = Student('Bob', 'male', 59)
>>> s.name = 'Tim' # OK
>>> s.score = 99 # OK
>>> s.grade = 'A'
Traceback (most recent call last):
...
AttributeError: 'Student' object has no attribute 'grade'
__slots__的目的是限制當(dāng)前類所能擁有的屬性,如果不需要添加任意動(dòng)態(tài)的屬性,使用__slots__也能節(jié)省內(nèi)存。
__call__在Python中,函數(shù)其實(shí)是一個(gè)對(duì)象:
>>> f = abs
>>> f.__name__
'abs'
>>> f(-123)
123
由于 f 可以被調(diào)用,所以,f 被稱為可調(diào)用對(duì)象。
所有的函數(shù)都是可調(diào)用對(duì)象。
一個(gè)類實(shí)例也可以變成一個(gè)可調(diào)用對(duì)象,只需要實(shí)現(xiàn)一個(gè)特殊方法__call__。
我們把 Person 類變成一個(gè)可調(diào)用對(duì)象:
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def __call__(self, friend):
print 'My name is %s...' % self.name
print 'My friend is %s...' % friend
現(xiàn)在可以對(duì) Person 實(shí)例直接調(diào)用:
>>> p = Person('Bob', 'male')
>>> p('Tim')
My name is Bob...
My friend is Tim...
單看 p('Tim') 你無法確定 p 是一個(gè)函數(shù)還是一個(gè)類實(shí)例,所以,在Python中,函數(shù)也是對(duì)象,對(duì)象和函數(shù)的區(qū)別并不顯著。
聯(lián)系客服