【示例】
def inc(x):
return x + 1
foo = 10
foo = inc(foo)
print(foo)
#如果想真的改變參數(shù)倘屹,那么可以使用一點(diǎn)小技巧密浑,即將值放在列表中def inc(x):
??? x[
0] = x[0] + 1
foo = [10] #把值放在列表中都许,相當(dāng)于C語(yǔ)言當(dāng)中的指針inc(foo)
print(foo)
#這樣就會(huì)只返回新值,代碼看起來(lái)也比較清晰蛾默。
【結(jié)果】
11
[11]
6.4.3 關(guān)鍵字參數(shù)和默認(rèn)值
目前使用的參數(shù)都叫做位置參數(shù),因?yàn)樗鼈兊奈恢煤苤匾?--事實(shí)上,比它們的名字更重要挖炬。本節(jié)引入的功能可以回避位置問(wèn)題,慢慢習(xí)慣之后状婶,會(huì)發(fā)現(xiàn)程序規(guī)模越大意敛,它們的作用也越大。
【示例】
def hello_1(greeting,name):
print('%s,%s!' % (greeting,name))
def hello_2(name,greeting):
print('%s,%s!' % (name,greeting))
#兩個(gè)代碼所實(shí)現(xiàn)的完全一樣的功能膛虫,只是參數(shù)名字反過(guò)來(lái)了hello_1('hello','world')
hello_2(
'hello','world')
#有時(shí),參數(shù)的順序是很難記住的稍刀×枚溃可以提供參數(shù)的名字:hello_1(greeting = 'hello',name = 'world')
hello_1(
name = 'world',greeting = 'hello')
#但參數(shù)名和值一定要對(duì)應(yīng)hello_2(greeting = 'hello',name = 'world')
#這類使用參數(shù)名提供的參數(shù)叫做關(guān)鍵字參數(shù)剧劝。作用主要是可以明確每個(gè)參數(shù)的作用暂论,也就避免了奇怪的函數(shù)調(diào)用。盡管這么做打的字多了些薪棒,但是顯然,每個(gè)參數(shù)的意義變得更加清晰。而且弄亂了參數(shù)的順序,對(duì)于程序的功能也沒(méi)有任何影響锐极。
【結(jié)果】
hello,world!
hello,world!
hello,world!
hello,world!
world,hello!
【示例】
#當(dāng)參數(shù)有默認(rèn)值的時(shí)候,調(diào)用的時(shí)候就不用提供參數(shù)了def hello_3(greeting = 'hello',name = 'world'):
print('%s,%s!' % (greeting,name))
hello_3()
hello_3(
'Greetings')
hello_3(
'Greetings','universe')
#位置和關(guān)鍵字參數(shù)是可以聯(lián)合使用的痴腌。把位置參數(shù)放置在前面就可以了剥悟。如果不這樣做,解釋器不知道它們到底誰(shuí)是誰(shuí)
#
注:只有在強(qiáng)制要求的參數(shù)個(gè)數(shù)比可修改的具有默認(rèn)值的參數(shù)個(gè)數(shù)少的時(shí)候,才使用上面提到的參數(shù)書(shū)寫(xiě)方法def hello_4(name,greeting = 'Hello',punctuation = '!'):
print('%s,%s%s' % (greeting, name,punctuation))
hello_4(
'Mars')
hello_4(
'Mars','Howdy')
hello_4(
'Mars','Howdy','...')
hello_4(
'Mars',punctuation = '.')
hello_4(
'Mars',greeting = 'Top of the moring to ya')
【結(jié)果】
hello,world!
Greetings,world!
Greetings,universe!
Hello,Mars!
Howdy,Mars!
Howdy,Mars...
Hello,Mars.
Top of the moring to ya,Mars!
6.4.4 收集參數(shù)
有時(shí)候讓用戶提供任意數(shù)量的參數(shù)是很有用的括堤。比如在名字存儲(chǔ)程序中瞬测,用戶每次只能存一個(gè)名字。如果能像下面這樣存儲(chǔ)多個(gè)名字耕肩,就更好了:
store(data,name1,name2,name3)
用戶可以給函數(shù)提供任意多的參數(shù)。
【示例】
def print_param(*params):
print(params)
#這里指定了一個(gè)參數(shù)腻格,但是前面加了個(gè)星號(hào)捣辆,為什么汽畴?
#
可以看到旧巾,結(jié)果作為元組打印出來(lái)耸序,因?yàn)槔锩嬗袀€(gè)逗號(hào)(長(zhǎng)度為1的元組有些怪,難道不是嗎菠齿?)。所以在參數(shù)前使用星號(hào)就能打印出元組坐昙,那么在Params中使用多個(gè)參數(shù)绳匀,看看會(huì)發(fā)生什么:print_param(1)
print_param(
1,2,3)
#參數(shù)前的星號(hào)將所有值放置在同一個(gè)元組中≌停可以說(shuō)是將這些值收集起來(lái)疾棵,然后使用。def print_param_2(title,*params):
print (title)
print(params)
print_param_2(
'Params:',1,2,3)
#沒(méi)問(wèn)題痹仙,所以星號(hào)的意思就是“收集其余的位置參數(shù)”是尔。如果不提供任何收集的元素,params就是個(gè)空元組print_param_2('Nothing:')
【結(jié)果】
(1,)
(1, 2, 3)
Params:
(1, 2, 3)
Nothing:
()
【示例】
#print_param_2('Hmm...',something= 42)
#看來(lái)不行开仰,所以需要另外一個(gè)能處理關(guān)鍵字參數(shù)的“收集”操作拟枚。def print_param_3(**params):
print(params)
print_param_3(
x = 1, y = 2,z = 3)
#返回的是字典而不是元組。def print_param_4(x,y,z = 3,*pospar,**keypar):
print(x,y,z)
print(pospar)
print(keypar)
print_param_4(
1,2,3,5,6,7,foo = 1,bar = 2)
#和我們期望的結(jié)果別無(wú)二致print_param_4(1,2,3,1,23,4,5,6,6,e = 2)
【結(jié)果】
{'z': 3, 'x': 1, 'y': 2}
1 2 3
(5, 6, 7)
{'foo': 1, 'bar': 2}
1 2 3
(1, 23, 4, 5, 6, 6)
{'e': 2}
【示例】
def init(d):
??? d[
'first'] = {}
??? d[
'middle'] = {}
??? d[
'last'] = {}
def lookup(data,label,name):
return data[label].get(name)
def store(data,*full_names): #full_names 代表元組众弓,可以存儲(chǔ)多個(gè)全名
??? for full_name infull_names:
??????? names = full_name.split(
' ') #這里names是列表(使用空格作為參數(shù)恩溅,或者不寫(xiě)參數(shù),則默認(rèn)是空格)
??????? if len(names)
== 2:
??????????? names.insert(
1,'')
??????????? labels =
'first','middle','last'
?????????? ?for label,namein zip(labels,names):
??????????????? people =lookup(data,label,name)
if people: #說(shuō)明原先值(列表)中不空谓娃,則使用append追加
??????????????????? people.append(full_name)
else: #說(shuō)明原先值(列表)中為空脚乡,則加入第一個(gè)
??????????????????? data[label][name] = [full_name] #(設(shè)定值為列表) key1是label,key2是name,value是列表滨达,可以存放好多全名d = {}
init(d)
store(d,
'Han Solo')
store(d,
'Luke Skywalker','Anakin
Skywalker')
list1 = lookup(d,
'last','Skywalker')
list2 = lookup(d,
'first','Han')
list3 = lookup(d,
'first','Ha')
print(list1)
print(list2)
print(list3)
【結(jié)果】
['Luke Skywalker', 'Anakin Skywalker']
['Han Solo']
None
6.4.5 反轉(zhuǎn)過(guò)程
如何將參數(shù)收集為元組和字典已經(jīng)討論過(guò)了奶稠,但是事實(shí)上,如果使用*和**的話捡遍,也可以執(zhí)行相反的操作锌订。即函數(shù)收集的逆過(guò)程。
【示例】
def add(x,y):
return x + y
#比如說(shuō)有個(gè)包含由兩個(gè)相加的數(shù)字組成的元組params = (1,2)
#這個(gè)過(guò)程多少有點(diǎn)像上一節(jié)中介紹的方法的逆過(guò)程画株。不是要收集參數(shù)瀑志,而是分配它們?cè)凇傲硪欢恕薄J褂?運(yùn)算符就簡(jiǎn)單了污秆,不過(guò)是在調(diào)用而不是在定義時(shí)使用:print(add(*params))
#對(duì)于參數(shù)列表來(lái)說(shuō)工作正常劈猪,只要擴(kuò)展的部分是最新的就可以×计矗可以使用同樣的技術(shù)來(lái)處理字典--使用雙星號(hào)運(yùn)算符战得。假設(shè)之前定義了hello_3,那么可以這樣使用:def hello_3(greeting = 'hello',name = 'world'):
print('%s,%s!' % (greeting,name))
params = {
'name': 'Sir Robin','greeting':'well
met'}
hello_3(**params)
#在定義或者調(diào)用時(shí)使用星號(hào)(或者雙興號(hào))僅傳遞元組或字典,所以可能沒(méi)遇到什么麻煩:def with_start(**kwds):
print(kwds['name'],'is',kwds['age'],'years old.')
def without_start(kwds):
print(kwds['name'],'is',kwds['age'],'years old')
args = {
'name':'Mr.
Gumby','age':42}
with_start(**args)
without_start(args)
#可以看到庸推,在with_starts中常侦,在定義和調(diào)用函數(shù)時(shí)都使用了星號(hào)浇冰。而在whthout_starts中,兩處都沒(méi)用聋亡,但得到了相同的效果肘习。所以星號(hào)只在定義函數(shù)(允許使用不定數(shù)目的參數(shù))或者調(diào)用(“分割”字典或者序列)時(shí)才有用。
【結(jié)果】
3
well met,Sir Robin!
Mr. Gumby is 42 years old.
Mr. Gumby is 42 years old
使用拼接(Splicing)操作符“傳遞”參數(shù)很有用坡倔,因?yàn)檫@樣一來(lái)就不用關(guān)心參數(shù)的個(gè)數(shù)之類的問(wèn)題
【示例】
def foo(x,y,z,m = 0,n = 0):
print(x,y,z,m,n)
def call_foo(*args,**kwds):
print("Calling foo!")
??? foo(*args,**kwds)
在調(diào)用超類的構(gòu)造函數(shù)時(shí)這個(gè)方法尤其有用漂佩。
6.4.6 練習(xí)使用參數(shù)
有了這么多提供和接受參數(shù)的方法,很容易犯渾吧罪塔!
【示例】
def story(**kwds):
return 'Once
upon a time. there was a %(job)s called %(name)s.' % kwds #%(job)s
job代表key
def power(x,y,*others):
if others:
print('Recieved redundant paramenters:',others)
return pow(x,y) #作用是求x的y次冪def interval(start,stop = None,step = 1):
'Imitates range() for step > 0'
??? if stop is None: #如果沒(méi)有為stop提供值
??????? start,stop = 0,start#指定參數(shù)
??? result = []
??? i = start
#計(jì)算start索引
??? while i < stop:
??????? result.append(i)
??????? i += step
return result
print('----------------------story----------------------------------')
print(story(job = 'king', name = 'Gumby'))
print(story(name= 'Sir Robin',job = 'brave knight'))
params = {
'job':'language','name':'Python'}
print(story(**params))
del params['job']
print(story(job = 'stroke of genius',**params)) #相當(dāng)于補(bǔ)上了一個(gè)keyprint('----------------------power----------------------------------')
print(power(2,3))
print(power(x = 3,y =2))
params = (
5,) * 2 #相當(dāng)于(5,5)print(power(*params)) #相當(dāng)于求5的5次冪print(power(3,3,'Hello, world'))
print('----------------------interval----------------------------------')
print(interval(10))
print(interval(1,5))
print(interval(3,12,4))
print(power(*interval(3,7))) #相當(dāng)于實(shí)參為3,4,(5,6)? 因?yàn)槭O碌姆旁谠M中
【結(jié)果】
----------------------story----------------------------------
Once upon a time. there was a king called Gumby.
Once upon a time. there was a brave knight called Sir Robin.
Once upon a time. there was a language called Python.
Once upon a time. there was a stroke of genius called Python.
----------------------power----------------------------------
8
9
3125
Recieved redundant paramenters: ('Hello, world',)
27
----------------------interval----------------------------------
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4]
[3, 7, 11]
Recieved redundant paramenters: (5, 6)
81
6.5 作用域
到底什么是變量投蝉?可以把它們看成是值的名字。在執(zhí)行x = 1賦值語(yǔ)句后征堪,名稱x引用到值1瘩缆。這就像用字典一樣。當(dāng)然佃蚜,變量和所對(duì)應(yīng)的值用的是個(gè)“不可見(jiàn)”的字典庸娱。
【示例】
x = 1
scope = vars()
print(scope['x'])
scope[
'x'] += 1
print(x)
print(scope['x'])
#一般來(lái)說(shuō),vars所返回的字典是不能修改的,換句話說(shuō)谐算,可能得不到想要的結(jié)果涌韩。
【結(jié)果】
1
2
2
這類“不可見(jiàn)字典”叫做命名空間或者作用域。除了全局作用域外氯夷,每個(gè)函數(shù)調(diào)用都會(huì)創(chuàng)建一個(gè)新的作用域臣樱。
【示例】
def foo():
x = 42
x = 1
foo()
print(x)
【結(jié)果】
1
這里foo函數(shù)改變了(重綁定)了變量x,但是最后腮考,x并沒(méi)有變雇毫。這是因?yàn)檎{(diào)用foo的時(shí)候,新的命名空間被創(chuàng)建了踩蔚,它作用于foo內(nèi)的代碼塊棚放。賦值語(yǔ)句x=42只在內(nèi)部作用域(局部命名空間)起作用。參數(shù)的工作原理類似于局部變量馅闽,所以用全局變量的名字作為參數(shù)名并沒(méi)有問(wèn)題飘蚯。
【示例】
def output(x):
print(x)
x =
1
y = 2
output(y)
【結(jié)果】
2
但是如果希望在函數(shù)的內(nèi)部訪問(wèn)全局變量怎么辦呢?而且只想讀取變量的值:
【示例】
def combine(parameter):
print(parameter+ external)
external =
'berry'
combine('Shrub')
【結(jié)果】
Shrubberry
#像這樣引用全局變量是很多錯(cuò)誤的引發(fā)原因福也,慎重使用全局變量局骤。
屏蔽(shadowing)的問(wèn)題:
讀取全局變量一般來(lái)說(shuō)并不是問(wèn)題,但是還是有個(gè)會(huì)出現(xiàn)問(wèn)題的事情暴凑,如果局部變量或者參數(shù)的名字和想要訪問(wèn)的全局變量名相同的話峦甩,就不能直接訪問(wèn)了。全局變量會(huì)被局部變量屏蔽现喳。
如果的確需要的話凯傲,可以使用globals函數(shù)獲取全局變量值犬辰,該函數(shù)的近親是vars,它可以返回全局變量的字典(locals返回局部變量的字典)冰单。
【示例】
def combine(parameter):
print(parameter
+ globals()['parameter'])
parameter =
'berry'
combine('Shrub')
【結(jié)果】
Shrubberry
【示例】
#接下來(lái)討論重綁定全局變量(使用變量引用其他新值)幌缝。如果在函數(shù)內(nèi)部將值賦予一個(gè)變量,它會(huì)自動(dòng)變成局部變量--除非告知Python將其聲明為全局變量诫欠。x = 1
def change_global():
global x
??? x = x +
1
change_global()
print(x)
【結(jié)果】
2
嵌套作用域:
Python的函數(shù)是可以嵌套的涵卵,也就是說(shuō)可以將一個(gè)函數(shù)放在另一個(gè)里面。
【示例】
def foo():
def bar():
print('Hello ,world!')
??? bar()
#嵌套一般并不那么有用呕诉,但它有一個(gè)很突出的應(yīng)用缘厢,例如需要用一個(gè)函數(shù)“創(chuàng)建”另一個(gè)吃度,也就意味著可以像下面這樣使用:def multiplier(factor):
def multiplyByFactor(number):
return number* factor
return multiplyByFactor
#一個(gè)函數(shù)位于另外一個(gè)里面甩挫,外層函數(shù)返回里層函數(shù)。函數(shù)本身被返回了椿每,但沒(méi)有調(diào)用伊者。重要的是返回的函數(shù)還可以訪問(wèn)它的定義所在的定義域。換句話說(shuō)间护,它“帶著”它的環(huán)境(和相關(guān)的局部變量)
#
每次調(diào)用外層函數(shù)亦渗,它內(nèi)部的函數(shù)都被重新綁定,factor變量每次都有一個(gè)新的值汁尺。由于Python的嵌套作用域法精,來(lái)自(multiplier)外部作用域的這個(gè)變量,稍后會(huì)被內(nèi)層函數(shù)訪問(wèn)痴突。double = multiplier(2) #綁定print(double(5)) #利用綁定的函數(shù)計(jì)算triple = multiplier(3) #重新綁定print(triple(3)) #使用print(multiplier(5)(4)) #重新綁定加使用
【結(jié)果】
10
9
20
6.6 遞歸
前面已經(jīng)學(xué)習(xí)了很多關(guān)于創(chuàng)建和調(diào)用函數(shù)的知識(shí)搂蜓。函數(shù)可以調(diào)用其他函數(shù)。令人驚訝的是辽装,函數(shù)可以調(diào)用自身帮碰。
遞歸:簡(jiǎn)單來(lái)說(shuō)就是引用的意思。
遞歸的定義包括它們自身定義內(nèi)容的引用拾积。
使用“遞歸”的幽默定義來(lái)定義遞歸一般來(lái)說(shuō)是不可行的殉挽,因?yàn)槟菢邮裁匆沧霾涣恕R粋€(gè)類似的定義如下:
【示例】
def recursion():
return recursion()
recursion()
【結(jié)果】
RecursionError:maximum recursion depth exceeded while calling a Python object
理論上講拓巧,它應(yīng)該永遠(yuǎn)運(yùn)行下去斯碌。然而每次調(diào)用函數(shù)都會(huì)用掉一點(diǎn)內(nèi)存,進(jìn)而程序會(huì)以一個(gè)“超過(guò)最大遞歸深度”的錯(cuò)誤信息結(jié)束肛度。
有用的遞歸函數(shù)包含以下幾個(gè)部分:
[if !supportLists]①? [endif]函數(shù)直接返回值時(shí)有基本實(shí)例(最小可能性問(wèn)題)
[if !supportLists]②? [endif]遞歸實(shí)例输拇,包括一個(gè)或者多個(gè)問(wèn)題最小部分的遞歸調(diào)用
這里的關(guān)鍵就是將問(wèn)題分解為小部分,遞歸不能永遠(yuǎn)繼續(xù)下去贤斜,因?yàn)樗偸且宰钚】赡苄詥?wèn)題結(jié)束策吠。
當(dāng)函數(shù)調(diào)用自身時(shí)逛裤,實(shí)際上運(yùn)行的是兩個(gè)不同的函數(shù)(或者說(shuō)是同一個(gè)函數(shù)有兩個(gè)不同的命名空間)。
6.6.1 兩個(gè)經(jīng)典:階乘和冪
【示例】
def factorial(n):
??? result = n
for i in range(1,n): #i的取值范圍是:1~n-1
??????? result *= i
return result
print(factorial(4))
【結(jié)果】
24
【示例】
def factorial(n):
if n == 1:
return 1
return n *
factorial(n - 1)
print(factorial(4))
【結(jié)果】
24
先看一個(gè)簡(jiǎn)單的例子:power(x,n)(x為n的冪次)是x自乘n-1次的結(jié)果猴抹。
【示例】
def power(x,n):
??? result =
1
for i in range(n):
??????? result *= x
return result
print(power(2,3))
【結(jié)果】
8
程序很小巧带族,接下來(lái)把它改變?yōu)檫f歸版本:
[if !supportLists]①? [endif]對(duì)于任意數(shù)字x來(lái)說(shuō),power(x,0)是1
[if !supportLists]②? [endif]對(duì)于任何大于0的數(shù)字來(lái)說(shuō)蟀给,power(x,n)是x乘以power(x,n-1)的結(jié)果
【示例】
def power(x,n):
if n == 0:
return 1
return x *
power(x,n-1)
print(power(2,3))
【結(jié)果】
8
那么遞歸有什么用蝙砌?就不能用循環(huán)代替嗎?答案是肯定的跋理,在大多數(shù)情況下可以使用循環(huán)择克,而且大多數(shù)情況下會(huì)更有效率。但是在多數(shù)情況下前普,遞歸更易讀—有時(shí)會(huì)大大提高可讀性—尤其當(dāng)讀程序的人懂得遞歸函數(shù)的定義的時(shí)候肚邢。
6.6.2 另外一個(gè)景點(diǎn) 二元查找
例如,當(dāng)提問(wèn)者可能在想一個(gè)1~100的數(shù)字拭卿,提問(wèn)者需要猜中它骡湖。當(dāng)然,提問(wèn)者可以耐心地猜上100次峻厚,但是真正需要猜多少次呢响蕴?
[if !supportLists]①? [endif]如果上下限相同,那么就是數(shù)字所在位置惠桃,返回浦夷。
[if !supportLists]②? [endif]否則找到兩者的中點(diǎn),查找數(shù)字是在左側(cè)還是在右側(cè)辜王。
這個(gè)遞歸例子的關(guān)鍵就是順序劈狐。
【示例】
def search(sequence,number,lower = 0,upper = None):
if upper is None:
??????? upper =
len(sequence)
- 1
if lower== upper:
assert number== sequence[upper]
return upper
else:
??????? middle = (lower + upper) //
2
if number> sequence[middle]:
return search(sequence,number,middle
+ 1,upper)
else:
return search(sequence,number,lower,middle)
#完全符合定義。如果lower == upper ,那么返回upper誓禁,也就是上限懈息。注意:程序假設(shè)(斷言)所查找的數(shù)字一定會(huì)被找到。如果沒(méi)有達(dá)到基本實(shí)例的話摹恰,先找到middle辫继,檢查數(shù)字是在左邊還是在右邊。seq = [34,67,8,123,4,100,95]
seq.sort()
print(seq)
print(search(seq,34))
print(search(seq,100))
#提示俗慈,標(biāo)準(zhǔn)庫(kù)中的bisect模塊可以非常有效地實(shí)現(xiàn)二元查找
【結(jié)果】
[4, 8, 34, 67, 95, 100, 123]
2
5
函數(shù)到處放
Python在應(yīng)對(duì)這類“函數(shù)式編程”方面有一些有用的函數(shù):map姑宽、filter和reduce函數(shù)(Python3.0中這些都被移至functools模塊中)」脍澹可以使用map函數(shù)將序列中的元素全部傳遞給一個(gè)函數(shù)炮车。
事實(shí)上,不是使用lambda函數(shù),而是在operator模塊引入對(duì)每個(gè)內(nèi)建運(yùn)算符的add函數(shù)瘦穆。使用operator模塊中的函數(shù)通常比用自己的函數(shù)更有效率纪隙。
第七章 更 加 抽 象
前面學(xué)習(xí)了Python主要的內(nèi)建對(duì)象類型(數(shù)字,字符串扛或、列表绵咱、元組和字典),以及內(nèi)建函數(shù)和標(biāo)準(zhǔn)庫(kù)用法熙兔,還有自定義函數(shù)的方式悲伶。
建立自己的對(duì)象類型可能很酷,但是做什么用呢住涉?創(chuàng)建自己的對(duì)象(尤其是類型或者成為類的對(duì)象)是Python的核心概念—非常核心麸锉,事實(shí)上,Python被稱為面向?qū)ο蟮恼Z(yǔ)言(和SmallTalk舆声、C++花沉、Java以及其他語(yǔ)言一樣。)本章將會(huì)介紹如何創(chuàng)建對(duì)象纳寂,以及多態(tài)主穗、封裝泻拦、特性毙芜、超類以及繼承的概念。
7.1 對(duì)象的魔力
對(duì)象最重要的有點(diǎn)包括一下幾個(gè)方面:
多態(tài):意味著可以對(duì)不同類的對(duì)象使用同樣的操作争拐,它們會(huì)像被“施了魔法一般”工作腋粥。
封裝:對(duì)外部世界隱藏對(duì)象的工作細(xì)節(jié)。
繼承:以普通的類為基礎(chǔ)建立專門的類對(duì)象架曹。
封裝和繼承會(huì)首先被介紹隘冲,因?yàn)樗鼈儽挥米鳜F(xiàn)實(shí)世界中的對(duì)象的模型。面向?qū)ο笤O(shè)計(jì)很有趣的特性是多態(tài)绑雄。它也是讓大多數(shù)人犯暈的特性展辞。以多態(tài)開(kāi)始。
7.1.1 多態(tài)
術(shù)語(yǔ)多態(tài)來(lái)自希臘語(yǔ):意思是“有多種形式”万牺。多態(tài)意味著計(jì)算不知道變量所引用的對(duì)象類型是什么罗珍,還是能對(duì)它進(jìn)行操作,而它會(huì)根據(jù)對(duì)象或類型的不同而表現(xiàn)出不同的行為脚粟。
#不要這樣做
def getPrice(object):
??? if isinstance(object,tuple):
?????? return object[1]
??? else:
?????? returnmagic_network_method(object)
#注:這里用isinstance函數(shù)查看對(duì)象是否為元組覆旱。如果是的話,就返回第2個(gè)元素核无,否則會(huì)調(diào)用一些“有魔力的”網(wǎng)絡(luò)方法扣唱。
但是程序還不是很靈活。如果某些聰明的程序員決定用十六進(jìn)制的字符串來(lái)表示價(jià)格,然后存儲(chǔ)在字典中的鍵“price”下面呢噪沙?
#不要這樣做
def getPrice(object):
??? if isinstance(object,tuple):
?????? return object[1]
??? elif isinstance(object,dict):
?????? return int(object[‘price’])
??? else:
?????? returnmagic_network_method(object)
現(xiàn)在是不是已經(jīng)考慮了所有的可能性炼彪?但是如果有人希望存儲(chǔ)在其他鍵下面的價(jià)格增加新的字典類型呢?可以再次更新getPrice函數(shù),但是這種工作還要做多長(zhǎng)時(shí)間虎忌?每次有人要實(shí)現(xiàn)價(jià)格對(duì)象的不同功能時(shí)宦芦,都要再次實(shí)現(xiàn)你的模塊。但是如果這個(gè)模塊已經(jīng)賣出去了或者轉(zhuǎn)到了其他更酷的項(xiàng)目中—那么如何應(yīng)付客戶齐疙?顯然這是個(gè)不靈活且不切實(shí)際的實(shí)現(xiàn)多種行為的代碼編寫(xiě)方式。
那么應(yīng)該怎么辦旭咽?可以讓對(duì)象自己進(jìn)行操作贞奋。這樣做會(huì)輕松很多。每個(gè)新的對(duì)象可以檢索和計(jì)算自己的價(jià)格并且返回結(jié)果—只需向它詢問(wèn)價(jià)格即可穷绵。這時(shí)多態(tài)就要出場(chǎng)了轿塔。
[if !supportLists]1.? [endif]多態(tài)和方法
object.getPrice()
綁定到對(duì)象特性上面的函數(shù)稱為方法。
‘a(chǎn)bc’.count(‘a(chǎn)’)
[1,2,’1’].count(‘a(chǎn)’)
【示例】
from random import choice
x = choice([
'Hello,world',[1,2,'e','e',4]])
print(x)
運(yùn)行后仲墨,變量x可能包含字符串’Hello,world!’勾缭,也有可能包含列表[1,2,’e’,’e’,4]---不用關(guān)心是哪個(gè)類型。
[if !supportLists]2.? [endif]多態(tài)的多種形式
任何不知道對(duì)象到底是什么類型目养,但是又要對(duì)對(duì)象“做點(diǎn)什么”的時(shí)候俩由,都會(huì)用到多態(tài)。這不僅限于方法—很多內(nèi)建運(yùn)算符和函數(shù)都有多態(tài)的性質(zhì)癌蚁。
【示例】
print(1+2)
print(
'Fish' + 'license')
【結(jié)果】
3
Fishlicense
【示例】
def length_message(x):
print('The length of',repr(x),"is",len(x))
length_message(
'Fnord')
length_message([
1,2,3])
【結(jié)果】
The length of 'Fnord' is 5
The length of [1, 2, 3] is 3
很多函數(shù)和運(yùn)算符都是多態(tài)的—你寫(xiě)的絕大多數(shù)程序可能都是幻梯。
如果可能的話,應(yīng)該盡力避免使用這些銷毀掉多態(tài)的方式努释。真正重要的是如何讓對(duì)象按照咱們希望的方式工作碘梢,不管它是否是正確的類型(或者類)。
7.1.2 封裝
封裝是對(duì)全局作用域中其他區(qū)域隱藏多余信息的原則伐蒂。使用對(duì)象而不用知道其內(nèi)部細(xì)節(jié)煞躬,它們都會(huì)幫助處理程序組件而不用過(guò)多關(guān)心多余細(xì)節(jié)。
但是封裝并不等同于多態(tài)逸邦。封裝是可以不關(guān)心對(duì)象是如何構(gòu)建的而直接進(jìn)行使用恩沛。
假設(shè)有個(gè)名叫OpenObject的類:
o = OpenObject() #This is how we create objects…
o.setName(‘Sir Lancelot’)
print(o.getName())
創(chuàng)建了一個(gè)對(duì)象后,將變量o綁定到該對(duì)象上昭雌「椿剑可以使用setName和getName方法。一切看起來(lái)很完美烛卧。但是假設(shè)變量o將它的名字存儲(chǔ)在全局變量globalName中佛纫,這意味著使用OpenObject類的實(shí)例時(shí)候妓局,不得不關(guān)心globalName的內(nèi)容。如果創(chuàng)建了多個(gè)OpenObject實(shí)例的話就會(huì)出現(xiàn)問(wèn)題呈宇,因?yàn)樽兞肯嗤门溃钥赡軙?huì)混淆:
o1 = OpenObject()
o2 = OpenObject()
o1.setName(‘Robin Hood’)
o2.getName()
可以看到,設(shè)定了一個(gè)名字后甥啄,其他的名字也就自動(dòng)設(shè)定了存炮。這可不是想要的結(jié)果◎诶欤基本上穆桂,需要將對(duì)象看做抽象,調(diào)用方法的時(shí)候不用關(guān)心其他的東西融虽∠硗辏可以將其作為特性存儲(chǔ)。
正如方法一樣有额,特性石對(duì)象內(nèi)部的變量般又;事實(shí)上更像是綁定到函數(shù)的特性。
r = ClosedObject()
r.setName(‘sir robin’)
r.getName()
可以看到新的對(duì)象的名稱已經(jīng)正確設(shè)置巍佑。但是第一個(gè)對(duì)象怎么樣了呢茴迁?
c.getName()
名字還在!這是因?yàn)閷?duì)象有了自己的狀態(tài)萤衰。對(duì)象的狀態(tài)由它的特性來(lái)描述堕义。對(duì)象的方法可以改變它的特性。所以就像是將一大堆函數(shù)捆在一起腻菇,并且給與它們?cè)L問(wèn)變量的權(quán)利胳螟。它們可以在函數(shù)調(diào)用之間保存的值昔馋。
7.1.3 繼承
繼承是另外一個(gè)懶惰(褒義)的行為筹吐。程序員不想把同一段代碼寫(xiě)好幾次。之前使用的函數(shù)避免了這種情況秘遏,但是現(xiàn)在又有個(gè)更微妙的問(wèn)題丘薛。如果已經(jīng)有了一個(gè)類,而又想建立一個(gè)非常類似的呢邦危?新的類可能只是添加幾個(gè)方法洋侨。在編寫(xiě)新類時(shí),又不想把舊類的代碼全部復(fù)制過(guò)去倦蚪∠<幔可以讓Rectangle從Shape類繼承方法。在Rectangle對(duì)象上調(diào)用draw方法時(shí)陵且,程序會(huì)自動(dòng)從shape類調(diào)用該方法裁僧。
7.2 類和類型
7.2.1 類到底是什么
可以將它或多或少地視為種類或者類型的同義詞个束。從很多方面來(lái)說(shuō),這就是類—一種對(duì)象聊疲。所有的對(duì)象都屬于某一個(gè)類茬底,稱為類的實(shí)例。
在面向?qū)ο蟪绦蛟O(shè)計(jì)中获洲,子類的關(guān)系是隱式的阱表,因?yàn)橐粋€(gè)類的定義取決于它所支持的方法。
例如贡珊,Bird可能支持fly方法最爬,而Penguin(Bird的子類)可能會(huì)增加個(gè)eatFish方法。當(dāng)創(chuàng)建Penguin類時(shí)门岔,可能會(huì)想要重寫(xiě)超類的fly方法烂叔,對(duì)于Penguin實(shí)例來(lái)說(shuō),這個(gè)方法要么什么也不做固歪,要么就產(chǎn)生異常蒜鸡,因?yàn)镻enguin(企鵝)不會(huì)fly(飛)
最近版本的Python中,基本類型和類之間的界限開(kāi)始模糊了牢裳》攴溃可以創(chuàng)建內(nèi)部類型的子類(或子類型),而這些類型的行為更類似于類蒲讯。
7.2.2 創(chuàng)建自己的類
終于來(lái)了忘朝!可以創(chuàng)建自己的類了!先來(lái)看一個(gè)簡(jiǎn)單的類:
【示例】
__metaclass__ = type #確定使用新式類class Person:
def setName(self,name):
self.name= name
def getName(self):
return self.name
def greet(self):
print("Hello, world! I'm %s" % self.name)
#注:所謂的舊式類和新式類之前是由區(qū)別的判帮。除非是Python3.0之前的版本中默認(rèn)附帶的代碼局嘁,否則再繼續(xù)使用舊式類已無(wú)必要。新式類的語(yǔ)法中晦墙,需要在模塊或者腳本開(kāi)始的地方放置賦值語(yǔ)句__metaclass__ = type(并不會(huì)在每個(gè)例子中顯式地包含這些語(yǔ)句)悦昵。除此之外,也有其他的方法晌畅,例如繼承新式類(比如object)但指。在Python 3.0中,舊式類的問(wèn)題不用擔(dān)心抗楔,因?yàn)樗鼈兏揪筒淮嬖诹恕?/p>
這個(gè)例子中棋凳,一切看起來(lái)都挺好,但是那個(gè)self參數(shù)看起來(lái)有點(diǎn)奇怪连躏。它是對(duì)于對(duì)象自身的引用剩岳。那么它是什么對(duì)象,讓我們創(chuàng)建一些實(shí)例來(lái)看看:
【實(shí)例】
foo= Person()
bar = Person()
foo.setName(
'Luke Skywalker')
bar.setName(
'Anakin Skywalker')
foo.greet()
bar.greet()
【結(jié)果】
Hello, world! I'm LukeSkywalker
Hello, world! I'm AnakinSkywalker
例子一目了然入热。在調(diào)用foo的setName和greet函數(shù)時(shí)拍棕,foo自動(dòng)將自己作為第一個(gè)參數(shù)傳入函數(shù)中----因此形象第命名為self疲迂。它總是對(duì)象自身。
顯然這就是self的用處和存在的必要性莫湘。沒(méi)有它的話尤蒿,成員方法就沒(méi)法訪問(wèn)他們要對(duì)其特性進(jìn)行操作的對(duì)象本身了。
和之前一樣幅垮,特性是可以在外部訪問(wèn)的腰池。
【示例】
print(foo.name)
bar.name =
'Yoda'
bar.greet()
【結(jié)果】
Luke Skywalker
Hello, world! I'm Yoda
提示:如果知道foo是Person的實(shí)例的話,那么還可以把foo.greet()看作Person.greet(foo)方便的寫(xiě)法
7.2.3 特性忙芒、函數(shù)和方法
self參數(shù)事實(shí)上正是方法和函數(shù)的區(qū)別示弓。方法(更專業(yè)一點(diǎn)可以稱為綁定方法)將它們的第一個(gè)參數(shù)綁定到所屬的實(shí)例上。因此這個(gè)參數(shù)可以不必提供呵萨。
【示例】
class Class:
def method(self):
print("I have a self!")
def function():
print("I don't ...")
instance = Class()
instance.method()
instance.method = function
instance.method()
【結(jié)果】
I have a self!
I don't ...
注意:self參數(shù)并不取決于調(diào)用方法的方式奏属,目前使用的是實(shí)例調(diào)用方法,可以隨意使用引用同一個(gè)方法的其他變量:
【示例】
class Bird:
??? song =
'Squaawk!'
??? def sing(self):
print(self.song)
bird = Bird()
bird.sing()
birdsong = bird.sing
birdsong()
【結(jié)果】
Squaawk!
Squaawk!
#盡管最后一個(gè)方法調(diào)用看起來(lái)與函數(shù)調(diào)用十分相似潮峦,但是變量birdsong引用綁定方法bird.sing上囱皿,也就意味著這還是對(duì)self參數(shù)的訪問(wèn)(也就是說(shuō),它仍舊綁定到類的相同實(shí)例上)忱嘹。
再論私有化嘱腥,默認(rèn)情況下,程序可以從外部訪問(wèn)一個(gè)對(duì)象的特性拘悦。再次使用前面討論過(guò)的有關(guān)封裝的例子齿兔。有些程序員覺(jué)得這樣做是可以的,但是有些人覺(jué)得這樣就破壞了封裝的原則础米。他們認(rèn)為對(duì)象的狀態(tài)對(duì)于外部應(yīng)該是完全隱藏的分苇。有人覺(jué)得為什么他們會(huì)站在如此極端的立場(chǎng)上。每個(gè)對(duì)象管理自己的特性還不夠嗎屁桑?為什么還要對(duì)外面世界隱藏呢医寿?畢竟如果能直接使用CloseObject的name特性的話,就不用使用setName和getName方法了掏颊。
關(guān)鍵在于糟红,其他程序員可能不知道(可能也不應(yīng)該知道)你的對(duì)象內(nèi)部的具體操作。例如乌叶,CloseObject可能會(huì)在其他對(duì)象更改自己名字的時(shí)候,給一些管理員發(fā)送郵件信息柒爸。但是如果直接用c.name設(shè)定名字會(huì)發(fā)生什么准浴?什么都沒(méi)發(fā)生,Email也沒(méi)法出去捎稚。為了避免這類事情的發(fā)生乐横,應(yīng)該使用私有(private)特性求橄,這是外部對(duì)象無(wú)法訪問(wèn),但getName和setName等訪問(wèn)器能訪問(wèn)的特性葡公。
Python并不直接支持私有方式罐农,而要靠程序員自己把握在外部進(jìn)行特性修改的時(shí)機(jī)。為了讓方法或者特性變?yōu)樗接校◤耐獠繜o(wú)法訪問(wèn))催什,只要在它的名字前面加上雙下劃線即可:
class Secretive:
def __inaccessible(self):
print("Bet you can't see me...")
def accessible(self):
print("The secret message is:")
self.__inaccessible()
現(xiàn)在涵亏,__inaccessible從外界是無(wú)法訪問(wèn)的,而在類內(nèi)部還能使用(比如從accessible)訪問(wèn):??????