python基礎教程_學習筆記9:抽象
抽象
懶惰即美德。
抽象和結構
抽象能夠節省大量工作,實際上它的作用還要更大。它是使得計算機程序能夠讓人讀懂的關鍵。
創建函數
函數能夠調用(可能包含參數,也就是放在圓括號裏的值),它運行某種行為而且返回一個值。一般來說,內建的callable函數能夠用來推斷函數是否可調用
>>>?import?math
>>>?y=1
>>>?x=math.sqrt
>>>?callable(x)
True
>>>?callable(y)
False
?
創建函數是組織程序的關鍵。
那麽如何定義函數呢?
使用def(或“函數定義”)語句就可以:
>>>?def?hello(name):
return?‘Hello,?‘?+?name?+?‘!‘
?
傳入不同的參數。得到不同的結果:
>>>?print?hello(‘signjing‘)
Hello,?signjing!
>>>?print?hello(‘jiao‘)
Hello,?jiao!
?
斐波那契數列的獲取方法(比如,前10項)為:
>>>?f=[0,1]
>>>?for?i?in?range(8):
f.append(f[-1]+f[-2])
?
>>>?print?f
[0,?1,?1,?2,?3,?5,?8,?13,?21,?34]
?
假設用函數的方法實現。則為:
>>>?def?fibs(num):
result=[0,1]
for?i?in?range(num-2):
result.append(result[-2]+result[-1])
return?result
?
運行結果:
>>>?fibs(10)
[0,?1,?1,?2,?3,?5,?8,?13,?21,?34]
>>>?fibs(16)
[0,?1,?1,?2,?3,?5,?8,?13,?21,?34,?55,?89,?144,?233,?377,?610]
?
return語句是用來從函數中返回值的。
記錄函數
假設想給函數寫文檔,讓後面使用該函數的人能理解的話。能夠增加凝視(以#開頭)。
另外一個方式是直接寫上字符串。
這裏字符串在其它地方可能會非常實用,比方在def語句後面(以及在模塊或類的開頭)。假設在函數的開頭寫下字符串,它就會成為函數的一部分進行存儲。稱為文檔字符串。
>>>?def?fibs(num):
‘fibs?is?a?funtion:*************‘
result=[0,1]
for?i?in?range(num-2):
result.append(result[-2]+result[-1])
return?result
?
>>>?fibs.__doc__
‘fibs?is?a?funtion:*************‘
?
註意:__doc__是函數屬性。
內建的help函數非常實用。
在交互式解釋器中使用它,就能夠得到關於函數,包含它的文檔字符串的信息。
>>>?help(fibs)
Help?on?function?fibs?in?module?__main__:
?
fibs(num)
????fibs?is?a?funtion:*************
?
並非真正函數的函數
數學意義上的函數,總在計算其參數後返回點什麽。python的有些函數卻並不返回不論什麽東西。
沒有return語句。或者雖有return語句但return後邊沒有跟不論什麽值的函數不返回值。
?
>>>?def?test():
print?"This?is?printed"
return
print?‘this?is?not‘
?
>>>?x=test()
This?is?printed
上述函數中的return語句僅僅起到結束函數的作用。
>>>?x
>>>?print?x
None
?
所以,全部的函數的確都返回了東西:當不須要它們返回值的時候。它們就返回None。
參數魔法
值從哪裏來
寫在def語句中函數名後面的變量通常叫函數的形式參數,而調用函數時提供的值是實際參數,或者成為參數。
我能改變參數嗎?
在函數內為參數賦予新值不會改變外部不論什麽變量的值。
>>>?def?try_to_change(n):
n="Hello?,?signjing"
?
>>>?say="Hello?,?jiao"
>>>?try_to_change(say)
>>>?say
‘Hello?,?jiao‘
?
字符串(以及數字和元組)是不可變的。即無法被改動。所以它們做參數的時候也就無需多做介紹。
但假設將可變的數據結構如列表做參數的時候會發生什麽:
?
>>>?def?change(n):
n[0]=‘signjing‘
?
>>>?names=[‘Li?lei‘,‘Han?meimei‘]
>>>?change(names)
>>>?names
[‘signjing‘,?‘Han?meimei‘]
?
以下不用函數調用再做一次:
>>>?names=[‘Li?lei‘,‘Han?meimei‘]
>>>?n=names
>>>?n[0]=‘signjing‘
>>>?names
[‘signjing‘,?‘Han?meimei‘]
?
之前也出現過這樣的情況:當兩個變量同一時候引用一個列表的時候,它們的確是同一時候引用一個列表。
假設想避免這樣的情況。能夠復制一個列表的副本。當在序列中做切片的時候,返回的切片總是一個副本。因此。假設你復制了整個列表的切片,將會得到一個副本:
>>>?n=names[:]
>>>?n
[‘Li?lei‘,?‘Han?meimei‘]
>>>?names
[‘Li?lei‘,?‘Han?meimei‘]
>>>?n?is?names
False
>>>?m=n
>>>?m?is?n
True
?
?
在某些語言(如c++、Ada)中,重綁定參數而且使這些改變影響到函數外的變量是非常尋常的事情。
但在python中是不可能的。函數僅僅能改動參數對象本身。
但假設參數不可變,如數字。又該怎麽辦呢?答案是沒有辦法。
這時候應該從函數中返回全部須要的值,假設值多於一個。則以元組形式返回。
比如。將變量數值增1的函數能夠這樣寫:
>>>?def?inc(x):return?x+1
?
>>>?foo=10
>>>?foo=inc(foo)
>>>?foo
11
?
假設真的想改變參數,能夠使用一點小技巧。即將值放置在列表中:
>>>?def?inc(x):x[0]=x[0]+1
?
>>>?foo=[10]
>>>?inc(foo)
>>>?foo
[11]
這樣就僅僅會返回新值。
?
keyword參數和默認值
眼下我們所使用的參數都叫做位置參數,由於它們的位置非常重要——其實比它們的名字更重要。
>>>?def?hello_1(greeting,name):
print?‘%s,%s‘?%(greeting,name)
?
>>>?def?hello_2(name,greeting):
print?‘%s,%s‘?%(name,greeting)
?
>>>?hello_1(‘hello‘,‘boy‘)
hello,boy
>>>?hello_2(‘hello‘,‘girl‘)
hello,girl
?
有些時候(尤其是參數非常多的時候)。參數的順序是非常難記住的。為了讓事情簡單些,能夠提供參數的名字:
>>>?hello_1(greeting=‘hello‘,name=‘boy‘)
hello,boy
>>>?hello_1(name=‘boy‘,greeting=‘hello‘)
hello,boy
但參數名和值一定要相應:
>>>?hello_2(name=‘boy‘,greeting=‘hello‘)
boy,hello
>>>?hello_2(greeting=‘hello‘,name=‘boy‘)
boy,hello
?
這類使用參數名提供的參數叫做keyword參數。
主要作用是明白每一個參數的作用。
keyword參數最厲害的地方在於能夠在函數中給參數提供默認值。當參數具有默認值的時候。調用的時候就不用提供參數了。能夠不提供、提供一些或者提供全部的參數:
>>>?def?hello_3(greeting=‘hello‘,name=‘world‘):
print?‘%s,%s!‘?%(greeting,name)
?
>>>?hello_3()
hello,world!
>>>?hello_3(‘Greeting‘)
Greeting,world!
>>>?hello_3(‘Greeting‘,‘universe‘)
Greeting,universe!
>>>?hello_3(name=‘boys‘)
hello,boys!
?
位置和keyword參數是能夠聯合使用的。
把位置參數放置在前面就能夠了。
?
註意:除非全然清楚程序的功能和參數的意義。否則應該避免混合使用位置參數和keyword參數。
?
收集參數
有時候讓用戶提供隨意數量的參數是非常實用的。
試著像以下這樣定義函數:
>>>?def?print_params(*params):
print?params
?
>>>?print_params(1,2)
(1,?2)
>>>?print_params(1,2,‘ab‘)
(1,?2,?‘ab‘)
?
參數前的星號將全部值放置在同一個元組中。能夠說是將這些值收集起來,然後使用。
>>>?def?print_params_2(title,*params):
print?title
print?params
?
>>>?print_params_2(‘Params:?‘,1,2,3)
Params:?
(1,?2,?3)
?
假設不提供不論什麽供收集的元素,params就是空元組:
>>>?print_params_2(‘Nothing:?‘)
Nothing:?
()
?
>>>?print_params_2(‘Hmm...‘,something=42)
?
Traceback?(most?recent?call?last):
??File?"<pyshell#73>",?line?1,?in?<module>
????print_params_2(‘Hmm...‘,something=42)
TypeError:?print_params_2()?got?an?unexpected?keyword?argument?‘something‘
我們須要另外一個能處理keyword參數的“收集”操作。
>>>?def?print_params_3(**params):
print?params
?
>>>?print_params_3(x=1,y=2,z=3)
{‘y‘:?2,?‘x‘:?1,?‘z‘:?3}
?
?
反轉過程
>>>?def?add(x,y):
return?x+y
?
>>>?params=(1,2)
>>>?add(*params)
3
在調用中使用而不是在定義中使用。
?
對於參數列表來說工作正常。僅僅要擴展到部分是最新的就能夠。能夠使用相同的技術來處理字典——使用雙星號運算符。
?
>>>?def?hello_3(greeting=‘hello‘,name=‘world‘):
print?‘%s,?%s!‘?%(greeting,name)
?
>>>?params={‘name‘:‘Sir?Robin‘,‘greeting‘:‘Well?met‘}
>>>?hello_3(*params)
name,?greeting!
>>>?hello_3(**params)
Well?met,?Sir?Robin!
?
星號僅僅在定義函數(同意使用不定數目的參數)或者調用(“切割”字典或者序列)時才實用。
?
作用域
變量和所相應的值用的是個“不可見”的字典。
實際上這麽說已經非常接近真實情況了。內建的vars函數能夠返回這個字典:
>>>?x=1
>>>?scope=vars()
>>>?scope[‘x‘]
1
>>>?scope[‘x‘]+=1
>>>?x
2
?
這類“不可見字典”叫做命名空間或者作用域。究竟有多少個命名空間?除了全局作用域外,每一個函數調用都會創建一個新的作用域。
?
參數的工作原理相似於局部變量,所以用全局變量的名字作為參數名並沒有問題。
?
假設須要在函數內部訪問全局變量,應該怎麽辦呢?而且僅僅想讀取變量的值(也就是說不想重綁定變量),一般來說是沒有問題的:
>>>?def?combine(parameter):
print?parameter+external
?
>>>?external=‘berry‘
>>>?combine(‘Shrub‘)
Shrubberry
?
讀取全局變量一般來說並非問題,可是還是有個會出問題的事情。假設局部變量或者參數的名字和想要訪問的全局變量名相同的話,就不能直接訪問了。全局變量會被局部變量屏蔽。
假設的確須要的話,能夠使用globals函數獲取全局變量值。該函數的近親是vars,它能夠返回全局變量的字典(locals返回局部變量的字典)。
?
重綁定全局變量(使變量引用其它新值):假設在函數內部將值賦予一個變量。它會自己主動成為局部變量——除非告知python將其聲明為全局變量。
>>>?x=1
>>>?def?change_global():
global?x
x=x+1
?
>>>?change_global()
>>>?x
2
遞歸
想到了一個笑話:
你要想理解遞歸,首先得理解遞歸。
?
好吧。有點冷,繼續熱乎的話題....
?
遞歸的定義(包含遞歸函數定義)包含它們自身定義內容的引用。
須要查找遞歸的意思。結果它告訴請參見遞歸,無窮盡也,一個相似的函數定義例如以下:
>>>?def?recursion():
return?recursion()
?
顯然它什麽也做不了,理論上講。它應該永遠運行下去。
?
由於每次調用函數都會用掉一點內存,在足夠的函數調用發生後,空間就不夠了,程序以一個“超過最大遞歸深度”的錯誤信息結束:
Traceback?(most?recent?call?last):
??File?"<pyshell#37>",?line?1,?in?<module>
????recursion()
??File?"<pyshell#36>",?line?2,?in?recursion
return?recursion()
......
??File?"<pyshell#36>",?line?2,?in?recursion
????return?recursion()
RuntimeError:?maximum?recursion?depth?exceeded
?
這類遞歸叫無窮遞歸,相似於while?True開始的無窮循環。中間沒有break或return語句。
?
實用的遞歸函數包含以下幾個部分:
當函數直接返回值時有基本實例(最小可能性問題);
遞歸實例,包含一個或者多個問題最小部分的遞歸調用;
?
兩個經典:階乘和冪
>>>?def?factorial(n):
result=n
for?i?in?range(1,n):
result*=i
return?result
?
>>>?factorial(5)
120
?
遞歸的實現方式:
>>>?def?factorial(n):
if?n==1:
return?1
else:
return?n*factorial(n-1)
>>>?factorial(4)
24
?
>>>?def?power(x,n):
result=1
for?i?in?range(n):
result*=x
return?result
?
>>>?power(5,3)
125
遞歸實現:
>>>?def?power(x,n):
if?n==0:
return?1
else:
return?x*power(x,n-1)
?
>>>?power(4,4)
256
還有一個經典:二元查找
此處略;
對象的魔力
創建自己的對象(尤其是類型或者被稱為類的對象)是python的核心概念——非常核心。
?
面向對象程序設計中的術語對象基本上能夠看作數據(特性)以及由一系列能夠存取、操作這些數據的方法所組成的集合。
對象最重要的長處包含以下幾個方面:
多態:能夠對不同類的對象使用相同的操作;
封裝:對外部世界隱藏對象的工作細節。
繼承:以普通的類為基礎建立專門的類對象。
類和類型
類究竟是什麽
類就是一種對象。全部的對象都屬於某一個類,成為類的實例。
?
當一個對象所屬的類是另外一個對象所屬類的子集時。前者就被稱為後者的子類。相反,後者就稱為前者的超類(基類)。
?
在面向對象程序設計中,子類的關系是隱式的,由於一個類的定義取決於它所支持的方法。定義子類僅僅是定義很多其它(也有可能是重載已經存在的)的方法的過程。
創建自己的類
>>>?__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
?
>>>?foo=Person()
>>>?bar=Person()
>>>?foo.setName(‘abc‘)
>>>?foo.getName()
‘abc‘
>>>?foo.greet()
Hello,world!I‘m?abc.
?
self是對於對象自身的引用。
沒有它,成員方法就沒法訪問他們要對其特性進行操作的對象本身了。
?
特效、函數和方法
默認情況下。程序能夠從外部訪問一個對象的特性。
為了讓方法或特性變為私有(從外部無法訪問),僅僅要在它的名字前面加上雙下劃線就可以:
>>>?class?Secretive:
def?__inaccessible(self):
print?"Bet?you?can‘t?see?me..."
def?accessible(self):
print?"The?secret?message?is:"
self.__inaccessible()
?
>>>?s.__inaccessible()
?
Traceback?(most?recent?call?last):
??File?"<pyshell#52>",?line?1,?in?<module>
????s.__inaccessible()
AttributeError:?‘Secretive‘?object?has?no?attribute?‘__inaccessible‘
>>>?s.accessible()
The?secret?message?is:
Bet?you?can‘t?see?me...
?
類的內部定義中,全部以雙下劃線開始的名字都被“翻譯”成前面加上單下劃線和類名的形式:
>>>?Secretive._Secretive__inaccessible
<unbound?method?Secretive.__inaccessible>
?
簡而言之,確保其它人不會訪問對象的方法和特性是不可能的,可是這類“名稱變化術”就是他們不應該訪問這些函數或者特性的強有力信號。
類的命名空間
類的定義其實就是運行代碼塊,這一點非常實用。
指定超類
將其它類名寫在class語句後的圓括號內能夠指定超類:
>>>?class?Filter:
def?init(self):
self.blocked=[]
def?filter(self,sequence):
return?[x?for?x?in?sequence?if?x?not?in?self.blocked]
?
>>>?class?SPAMFilter(Filter):
def?init(self):
self.blocked=[‘SPAM‘]
?
?
Filter是個用於過濾序列的通用類。其實它不能過濾不論什麽東西:
>>>?f=Filter()
>>>?f.init()
>>>?f.filter([1,3,4])
[1,?3,?4]
?
Filter類的用處在於它能夠用作其它類的基類(超類),能夠將序列中的“SPAM”過濾出去。
>>>?s=SPAMFilter()
>>>?s.init()
>>>?s.filter([‘abc‘,‘SPAM‘,"SPAM",‘SPAM‘,‘signjing‘])
[‘abc‘,?‘signjing‘]
調查繼承
想要查看一個類是否是還有一個的子類。能夠使用內建的issubclass函數:
>>>?issubclass(SPAMFilter,Filter)
True
>>>?issubclass(Filter,SPAMFilter)
False
?
假設想要知道已知類的基類(們),能夠直接使用它的特殊特性__bases__:
>>>?SPAMFilter.__bases__
(<class?‘__main__.Filter‘>,)
>>>?Filter.__bases__
(<type?‘object‘>,)
?
相同。還能使用isinstance方法檢查一個對象是否是一個類的實例:
>>>?isinstance(s,SPAMFilter)
True
>>>?isinstance(s,Filter)
True
>>>?isinstance(s,str)
False
?
S是SPAMFilter類的(直接)成員。但也是Filter類的間接成員,由於SPAMFilter是Filter的子類。
?
假設想知道一個對象屬於哪個類,能夠使用__class__特性:
>>>?s.__class__
<class?‘__main__.SPAMFilter‘>
多個超類
>>>?class?Calculator:
def?calculate(self,expression):
self.value=eval(expression)
?
>>>?class?Talker:
def?talk(self):
print?‘Hi,my?value?is‘,self.value
?
>>>?class?TalkingCalculator(Calculator,Talker):
pass
?
超類能夠有多個。
在這裏,子類不做不論什麽事,從自己的超類繼承全部的行為。
這樣的行為成為多重繼承。是個非常實用的工具。
使用多重繼承時,有個須要註意的地方。假設一個方法從多個超類繼承,必須要註意一下超類的順序:
先繼承的類中的方法會重寫後繼承的類中的方法。
python基礎教程_學習筆記9:抽象