Python入門基礎第十三課--抽象(一)

    1.前言

    這章的內容可能有點多,也有可能有些難以理解。這一章,我們會介紹到:抽象、函數定義、參數、作用域、遞歸、函數式編程。其中有的是新的概念,有的是原來的舊的概念。不論是新的還是舊的,都需要我們去及時關注和整理,也需要我們去掌握。難理解的概念我會說的很詳細,請大家不要嫌麻煩,認真理解。爲後面重要內容的學習奠定一定的基礎。下面就開始我們的學習!

    2.抽象與結構

    我們首先來看一個例子,是我們熟悉的斐波那契數列的程序實現:

#Fibonacci number
#author youzi
#coding:utf-8
fibs=[0,1]
num=int(raw_input("How many Fibonacci number do you want: "))

for i in range(num-2):
    fibs.append(fibs[-2]+fibs[-1])
print fibs

    在這裏,我稍微地設計的人性化了一點。用戶可以輸入自己的數字作爲動態範圍的長度使用,實現方式當然是採用我們熟悉的for循環,一目瞭然。再來看下面這樣的設計:

num=raw_input('How many numbers do you want? ')
print fibs(num)

    這種程序的設計更加抽象一些,但是很清楚。讀入數值,然後打印出結果。事實上計算斐波那契數列是由一種抽象的方式來完成的:只需要告訴計算機去做就好,不同特別說明應該怎麼做,名爲fibs的函數將會被創建,然後在需要的地方調用它就可以咯。如果這個函數要被調用很多次,這麼做的話會很省力!

    抽象的這種形式可以讓人們很輕鬆的讀懂計算機程序,有時候你只需要關注關鍵的幾步就能得到你想要的結果,而不是裏面實現的每一步我都需要去額外的特別關注,那豈不是很麻煩,失去了抽象設計的意義。

    3.創建函數

    在前面的入門基礎第三課裏面,我們簡單的介紹了一下函數的內容,函數可以被創建,也可以被調用。我們也講了函數調用的方法,下面再來看一些用法。

    還是前面的斐波那契數列創建例子,我們將它封裝到一個具體的函數裏面:

def fibs(num):
    result=[0,1]
    for i in range(num-2):
        result.append(result[-2]+result[-1])
    return result

print fibs(10) 

    有了這樣的函數以後,我們再想生成斐波那契數列的時候就可以直接調用fibs函數,像fibs(10)、fibs(15)這樣的調用就可以叨叨我們的目的,這也是將具體的實現過程封裝到了函數裏面,也是抽象形式的一種體現。需要注意的是這裏的函數名字和裏面的變量都是隨自己取的,但是函數結尾的return語句是不可或缺的,用來從函數中返回值的。

   3.1文檔化函數

    假如你爲了實現某個特定的功能編寫了一個比較難以理解的函數,你想讓用戶在調用或者查看的時候能夠清楚了理解它是什麼功能,你就需要給這個函數編寫一定的介紹文檔,我們稱爲文檔字符串,具體實現方式如下所示:
def square(x):
    'Calculates the square of the number x.'
     return x*x

    然後你就可以去調用它了,文檔字符串可以按如下方式訪問:

>>>square.__doc__
'Calculates the square of the number x.'

    如果你覺得這樣方式有些麻煩,還有個很好用的內建函數help,是很有用的。在shell裏面去使用它,就可以得到關於函數,包括它的文檔字符串的信息。

    3.2參數

    3.2.1參數從何而來

    函數被創建好以後,傳入的值是從哪裏來的呢?一般來講不用太關注這些問題,能保證函數在被提供給可接受參數的時候正常工作就行,參數的傳入要是不正確的話會導致程序失敗。

    函數通過它的參數獲取到值,那麼這些值我們可以改變嗎?參數只是變量而已,你在函數內爲參數賦予新的值不會改變外部任何變量的值。來看下面這些例子:                   

>>> def try_to_change(n):n='Mr. Gumby'
... 
>>> name='Ms .Entity'
>>> try_to_change(name)
>>> name
'Ms .Entity'

    再來看看具體的工作方式:

>>> name='Mrs. Entity'
>>> n=name
>>> n='Mr. Gumby'
>>> name
'Mrs. Entity'

    結果是顯而易見的,我們只是將name參數的值傳給了n,並沒有改變name參數的值,當在函數內部把參數重新綁定的時候,函數外的變量不會受到影響。

    前面說過,字符串、數字和元組是不能被修改的我們只能用新的值去覆蓋它們。但是除了上面說過的,不要忘了還有列表,當我們利用列表作爲參數的時候回發生什麼,我們來看看:

>>> def change(n): n[0]='Mr.Gumby'
... 
>>> names=['youzi','Alice']
>>> change(names)
>>> names
['Mr.Gumby', 'Alice']

    哎呦,怎麼和前面說的不一樣,參數被改變了。這就是這個例子和前面說的例子重要的區別。我們來看看它的具體實現方式:

>>> names=['youzi','Alice']
>>> n=names
>>> n[0]='Mr.Gumby'
>>> names
['Mr.Gumby', 'Alice']

    當兩個變量同時指向一個列表的時候,它們確實是同時引用一個列表。如果要避免這種情況,可以複製一個列表的副本。來看看:

>>> names
['Mr.Gumby', 'Alice']
>>> n=names[:] #製作一個names的副本
>>> n is names #同一性
False
>>> n==names #值相等
True

    在這樣的程序執行完以後,n和names是完全獨立的兩個列表,但是兩個的值是相等的。參數n相當於是names的一個副本,如果現在去和上面一樣修改n就不會影響到names。具體的實現大家可以仿照上面自己實現。

    使用函數改變數據結構是這一種將程序抽象化的好方法,抽象的要點就是隱藏更新時候繁瑣的細節,這個過程可以用函數來實現,來看看下面的例子,我們利用這樣函數來初始化數據結構。

def init(data):
     data['first']={}
     data['middle']={}
     data['last']={}

    然後利用這樣方式來使用它:

>>>storage={}
>>>init(storage)
>>>storage
{'middle':{}, 'last':{}, 'first':{}}

    我們可以很清楚的看到,函數承包了初始化數據結構的工作,讓代碼更易讀。

    假如我們碰到不可變的參數該怎麼辦?不好意思咩,沒有辦法,這個時候你應該從函數中返回所有你需要的值。但是如果你真的想改變一下參數,可以用一下下面的小技巧。

>>> def inc(x): x[0]=x[0]+1
... 
>>> foo=[10]
>>> inc(foo)
>>> foo
[11]

    看咯,這樣就能返回新的值,就是改變了參數。

    3.2.2關鍵字參數和位置參數

    目前爲止我們使用過的參數都是位置參數,因爲它們的位置很重要,事實上比它們的名字更加重要。還有一類是關鍵字參數,我們可以使用關鍵字參數爲函數提供默認值,很方便。位置參數和關鍵字參數是可以聯合使用的,但是使用的時候把位置參數放在最前面就可以了,如果不這樣做,解釋器就不會知道它們到底誰是誰,也就是不知道它們應該處的位置在哪裏。我們直接來看一些例子,會更加明瞭。

>>> def hello_1(greeting,name): print '%s, %s!' %(greeting,name) #一般一號問候函數
... 
>>> def hello_2(greeting,name): print '%s, %s!' %(name,greeting) #一般二號問候函數
... 
>>> hello_1('Hello','world') #函數測試輸出
Hello, world!
>>> hello_2('Hello','world') #函數測試輸出
world, Hello!
>>> hello_1(name='world',greeting='Hello') #關鍵字參數輸出
Hello, world!
>>> hello_2(greeting='Hello',name='world') #不同的關鍵字參數輸出
world, Hello!
>>> def hello_3(greeting='Hello',name='world'): print '%s, %s' %(greeting,name) #關鍵字參數提供默認值的三號問候函數
... 
>>> hello_3() #不給任何參數的默認參數輸出
Hello, world
>>> hello_3('Greetings') #只含位置參數的輸出
Greetings, world
>>> hello_3('Greetings','universe') #位置參數輸出
Greetings, universe
>>> hello_3(name='Gumby') #只提供name參數,默認greeting參數的輸出
Hello, Gumby

    3.2.3參數收集

    有時候讓用戶一次性提供任意數量的參數是很有用的,我們來看一看例子就會明瞭。

>>> def print_params(*params): print params #利用*來進行參數收集
... 
>>> print_params('Testing')
('Testing',)
>>> print_params(1,2,3)
(1, 2, 3)
>>> def print_params3(**params): print params #利用**來進行參數收集
... 
>>> print_params3(x=1,y=2,z=3)
{'y': 2, 'x': 1, 'z': 3}
    對比上面兩種參數收集的方式和最後測試打印結果的區別,你應該不難看出兩者的差別。所以,沒有問題。星號的意思就將所有的位置參數放置在同一個元組裏面,可以說是將這些值收集起來,然後在需要的地方使用。而兩個星號是用來處理關鍵字的收集操作。

    3.2.4參數收集的逆過程

    我們說*和**的方法可以用來收集參數,那麼能不能用來進行逆過程的操作呢?逆過程我們說是這樣的--不是收集參數,而是分配它們在“另一端”。不是在定義的時候使用,而是在調用的時候使用。我們來看看:

>>> def add(x,y): return x+y
... 
>>> params=(1,2)
>>> add(*params)
3
>>> add(params)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: add() takes exactly 2 arguments (1 given)

  上面這個例子還不明顯的話,再來看:

>>> params={'name':'Youzi','greeting':'Hello'}
>>> hello_3(**params)
Hello, Youzi

    4.參數使用練習

#coding:utf-8
#author:youzi
def story(**kwds):
    return 'Once upon a time, there was a %(job)s called %(name)s. ' %kwds

def power(x,y,*others):
    if others:
        print 'Received reduntant parameters: ', others
    return pow(x,y)


def interval(start,stop=None,step=1):
    'Imitates range() for step>0'
    if stop is None:
        start, stop=0,start
    result=[]
    i=start
    while i<stop:
        result.append(i)
        i+=step
    return result

print story(job='king',name='Gumby')
print story(name='youzi',job='brave king')
print pow(2,3)
print power(2,3,'Hello,World')
print interval(10)
print interval(1,5)
print interval(3,12,4)
print power(*interval(3,7))
    上面的程序都是很好理解的,自己動手實踐,得出結果詳細分析。你會有所收穫。
    抽象這部分內容還沒完,下一章節的內容我們將繼續講述。


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