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]

這樣就只會返回新值。

 

關鍵字參數和默認值

目前我們所使用的參數都叫做位置參數,因爲它們的位置很重要——事實上比它們的名字更重要。

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

 

這類使用參數名提供的參數叫做關鍵字參數。主要作用是明確每個參數的作用。

關鍵字參數最厲害的地方在於可以在函數中給參數提供默認值。當參數具有默認值的時候,調用的時候就不用提供參數了。可以不提供、提供一些或者提供所有的參數:

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

 

位置和關鍵字參數是可以聯合使用的。把位置參數放置在前面就可以了。

 

注意:除非完全清楚程序的功能和參數的意義,否則應該避免混合使用位置參數和關鍵字參數。

 

收集參數

有時候讓用戶提供任意數量的參數是很有用的。試着像下面這樣定義函數:

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

我們需要另外一個能處理關鍵字參數的“收集”操作。

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

 

超類可以有多個。

在這裏,子類不做任何事,從自己的超類繼承所有的行爲。

這種行爲成爲多重繼承,是個非常有用的工具。

使用多重繼承時,有個需要注意的地方。如果一個方法從多個超類繼承,必須要注意一下超類的順序:

先繼承的類中的方法會重寫後繼承的類中的方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章