1. 程式人生 > >python基礎教程_學習筆記9:抽象

python基礎教程_學習筆記9:抽象

ror ssi main ria lob ram 以及 line hang

版權聲明:本文為博主原創文章。未經博主同意不得轉載。 https://blog.csdn.net/signjing/article/details/30745465

抽象

懶惰即美德。

抽象和結構

抽象能夠節省大量工作,實際上它的作用還要更大。它是使得計算機程序能夠讓人讀懂的關鍵。

創建函數

函數能夠調用(可能包含參數,也就是放在圓括號裏的值),它運行某種行為而且返回一個值。一般來說,內建的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開始的無窮循環。中間沒有breakreturn語句。

?

實用的遞歸函數包含以下幾個部分:

當函數直接返回值時有基本實例(最小可能性問題);

遞歸實例,包含一個或者多個問題最小部分的遞歸調用;

?

兩個經典:階乘和冪

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

?

SSPAMFilter類的(直接)成員。但也是Filter類的間接成員,由於SPAMFilterFilter的子類。

?

假設想知道一個對象屬於哪個類,能夠使用__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:抽象