2018-01-17 Python學(xué)習(xí)第五天

【示例】

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):??????

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蒲凶,一起剝皮案震驚了整個(gè)濱河市气筋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌旋圆,老刑警劉巖宠默,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異灵巧,居然都是意外死亡搀矫,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門刻肄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)艾君,“玉大人,你說(shuō)我怎么就攤上這事肄方”ⅲ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵权她,是天一觀的道長(zhǎng)虹茶。 經(jīng)常有香客問(wèn)我,道長(zhǎng)隅要,這世上最難降的妖魔是什么蝴罪? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮步清,結(jié)果婚禮上要门,老公的妹妹穿的比我還像新娘。我一直安慰自己廓啊,他們只是感情好欢搜,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著谴轮,像睡著了一般炒瘟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上第步,一...
    開(kāi)封第一講書(shū)人閱讀 51,301評(píng)論 1 301
  • 那天疮装,我揣著相機(jī)與錄音缘琅,去河邊找鬼。 笑死廓推,一個(gè)胖子當(dāng)著我的面吹牛刷袍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播樊展,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼呻纹,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了滚局?” 一聲冷哼從身側(cè)響起居暖,我...
    開(kāi)封第一講書(shū)人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎藤肢,沒(méi)想到半個(gè)月后太闺,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嘁圈,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年省骂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片最住。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡钞澳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出涨缚,到底是詐尸還是另有隱情轧粟,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布脓魏,位于F島的核電站兰吟,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏茂翔。R本人自食惡果不足惜混蔼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望珊燎。 院中可真熱鬧惭嚣,春花似錦、人聲如沸悔政。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)卓箫。三九已至载矿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間烹卒,已是汗流浹背闷盔。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留旅急,地道東北人逢勾。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像藐吮,于是被迫代替她去往敵國(guó)和親溺拱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354