函數(shù)(2)

2018-02-24 15:48 更新

在上一節(jié)中,已經(jīng)明確了函數(shù)的基本結(jié)構(gòu)和初步的調(diào)用方法。但是,上一節(jié)中寫的函數(shù),還有點缺憾,不知道讀者是否覺察到了。我把結(jié)果是用print語句打印出來的。這是實際編程中廣泛使用的嗎?肯定不是。因為函數(shù)在編程中,起到一段具有抽象價值的代碼作用,一般情況下,用它得到一個結(jié)果,這個結(jié)果要用在其它的運算中。所以,不能僅僅局限在把某個結(jié)果打印出來。所以,函數(shù)必須返回一個結(jié)果。

結(jié)論:函數(shù)要有返回值,也必須有返回值。

返回值

為了能夠說明清楚,先編寫一個函數(shù)。還記得斐波那契數(shù)列嗎?我打算定義一個能夠得到斐波那契數(shù)列的函數(shù),從而實現(xiàn)可以實現(xiàn)任意的數(shù)列。你先想想,要怎么寫?

參考代碼:

#!/usr/bin/env python
# coding=utf-8

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

if __name__ == "__main__":
    lst = fibs(10)
    print lst

把含有這些代碼的文件保存為名為20202.py的文件。在這個文件中,首先定義了一個函數(shù),名字叫做fibs,其參數(shù)是輸入一個整數(shù)。在后面,通過lst = fibs(10)調(diào)用這個函數(shù)。這里參數(shù)給的是10,就意味著要得到n=10的斐波那契數(shù)列。

運行后打印數(shù)列:

$ python 20202.py
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

當(dāng)然,如果要換n的值,只需要在調(diào)用函數(shù)的時候,修改一下參數(shù)即可。這才體現(xiàn)出函數(shù)的優(yōu)勢呢。

觀察fibs函數(shù),最后有一個語句return result,意思是將變量result的值返回。返回給誰呢?一般這類函數(shù)調(diào)用的時候,要通過類似lst = fibs(10)的語句,那么返回的那個值,就被變量lst貼上了,通過lst就能得到該值。如果沒有這個賦值語句,雖然函數(shù)照樣返回值,但是它飄忽在內(nèi)存中,我們無法得到,并且最終還被當(dāng)做垃圾被python回收了。

注意:上面的函數(shù)之返回了一個返回值(是一個列表),有時候需要返回多個,是以元組形式返回。

>>> def my_fun():
...     return 1,2,3
... 
>>> a = my_fun()
>>> a
(1, 2, 3)

有的函數(shù),沒有renturn,一樣執(zhí)行完畢,就算也干了某些活兒吧。事實上,不是沒有返回值,也有,只不過是None。比如這樣一個函數(shù):

>>> def my_fun():
...     print "I am doing somthin."
... 

我在交互模式下構(gòu)造一個很簡單的函數(shù),注意,我這是構(gòu)造了一個簡單函數(shù),如果是復(fù)雜的,千萬不要在交互模式下做。如果你非要做,是能嘗到苦頭的。

這個函數(shù)的作用就是打印出一段話。也就是執(zhí)行這個函數(shù),就能打印出那段話,但是沒有return。

>>> a = my_fun()
I am doing somthin.

我們再看看那個變量a,到底是什么

>>> print a
None

這就是這類只干活兒,沒有return的函數(shù),返回給變量的是一個None。這種模樣的函數(shù),通常不用上述方式調(diào)用,而采用下面的方式,因為他們返回的是None,似乎這個返回值利用價值不高,于是就不用找一個變量來接受返回值了。

>>> my_fun()
I am doing somthin.

特別注意那個return,它還有一個作用。觀察下面的函數(shù)和執(zhí)行結(jié)果,看看能不能發(fā)現(xiàn)它的另外一個作用。

>>> def my_fun():
...     print "I am coding."
...     return
...     print "I finished."
... 
>>> my_fun()
I am coding.

看出玄機了嗎?在函數(shù)中,本來有兩個print語句,但是中間插入了一個return,僅僅是一個return。當(dāng)執(zhí)行函數(shù)的時候,只執(zhí)行了第一個print語句,第二個并沒有執(zhí)行。這是因為第一個之后,遇到了return,它告訴函數(shù)要返回,即中斷函數(shù)體內(nèi)的流程,離開這個函數(shù)。結(jié)果第二個print就沒有被執(zhí)行。所以,return在這里就有了一個作用,結(jié)束正在執(zhí)行的函數(shù)。有點類似循環(huán)中的break的作用。

函數(shù)中的文檔

“程序在大多數(shù)情況下是給人看的,只是偶爾被機器執(zhí)行以下。”所以,寫程序必須要寫注釋。前面已經(jīng)有過說明,如果用#開始,python就不執(zhí)行那句(python看不到它,但是人能看到),它就作為注釋存在。

除了這樣的一句之外,一般在每個函數(shù)名字的下面,還要寫一寫文檔,以此來說明這個函數(shù)的用途。

#!/usr/bin/env python
# coding=utf-8

def fibs(n):
    """
    This is a Fibonacci sequence.
    """
    result = [0,1]
    for i in range(n-2):
        result.append(result[-2] + result[-1])
    return result

if __name__ == "__main__":
    lst = fibs(10)
    print lst

在這個函數(shù)的名稱下面,用三個引號的方式,包裹著對這個函數(shù)的說明,那個就是函數(shù)文檔。

還記得在《自省》那節(jié)中,提到的__doc__嗎?對于函數(shù),它的內(nèi)容就來自這里。

>>> def my_fun():
...     """
...     This is my function.
...     """
...     print "I am a craft."
... 
>>> my_fun.__doc__
'\n    This is my function.\n    '

如果在交互模式中用help(my_fun)得到的也是三個引號所包裹的文檔信息。

Help on function my_fun in module __main__:

my_fun()
    This is my function.

參數(shù)和變量

參數(shù)

雖然在上一節(jié),已經(jīng)知道如何通過函數(shù)的參數(shù)傳值,如何調(diào)用函數(shù)等。但是,這里還有必要進(jìn)一步討論參數(shù)問題。在別的程序員嘴里,你或許聽說過“形參”、“實參”、“參數(shù)”等名詞,到底指什么呢?

在定義函數(shù)的時候(def來定義函數(shù),稱為def語句),函數(shù)名后面的括號里如果有變量,它們通常被稱為“形參”。調(diào)用函數(shù)的時候,給函數(shù)提供的值叫做“實參”,或者“參數(shù)”。

其實,根本不用區(qū)分這個,因為沒有什么意義,只不過類似孔乙己先生知道茴香豆的茴字有多少種寫法罷了。但是,我居然碰到過某公司面試官問這種問題。

在本教程中,把那個所謂實參,就稱之為值(或者數(shù)據(jù)、或者對象),形參就籠統(tǒng)稱之為參數(shù)(似乎不很合理,但是接近數(shù)學(xué)概念)。

比較參數(shù)和變量

參數(shù)問題就算說明白了,糊涂就糊涂吧,也沒有什么關(guān)系。不過,對于變量和參數(shù),這兩個就不能算糊涂賬了。因為它容易讓人糊涂了。

在數(shù)學(xué)的函數(shù)中y = 3x + 2,那個x叫做參數(shù),也可以叫做變量。但是,在編程語言的函數(shù)中,與此有異。

先參考一段來自微軟網(wǎng)站的比較高度抽象,而且意義涵蓋深遠(yuǎn)的說明。我摘抄過來,看官讀一讀,是否理解,雖然是針對VB而言的,一樣有啟發(fā)。

參數(shù)和變量之間的差異 (Visual Basic)

多數(shù)情況下,過程必須包含有關(guān)調(diào)用環(huán)境的一些信息。執(zhí)行重復(fù)或共享任務(wù)的過程對每次調(diào)用使用不同的信息。此信息包含每次調(diào)用過程時傳遞給它的變量、常量和表達(dá)式。

若要將此信息傳遞給過程,過程先要定義一個形參,然后調(diào)用代碼將一個實參傳遞給所定義的形參。 您可以將形參當(dāng)作一個停車位,而將實參當(dāng)作一輛汽車。 就像一個停車位可以在不同時間停放不同的汽車一樣,調(diào)用代碼在每次調(diào)用過程時可以將不同的實參傳遞給同一個形參。

形參表示一個值,過程希望您在調(diào)用它時傳遞該值。

當(dāng)您定義 Function 或 Sub 過程時,需要在緊跟過程名稱的括號內(nèi)指定形參列表。對于每個形參,您可以指定名稱、數(shù)據(jù)類型和傳入機制(ByVal (Visual Basic) 或 ByRef (Visual Basic))。您還可以指示某個形參是可選的。這意味著調(diào)用代碼不必傳遞它的值。

每個形參的名稱均可作為過程內(nèi)的局部變量。形參名稱的使用方法與其他任何變量的使用方法相同。

實參表示在您調(diào)用過程時傳遞給過程形參的值。調(diào)用代碼在調(diào)用過程時提供參數(shù)。

調(diào)用 Function 或 Sub 過程時,需要在緊跟過程名稱的括號內(nèi)包括實參列表。每個實參均與此列表中位于相同位置的那個形參相對應(yīng)。

與形參定義不同,實參沒有名稱。每個實參就是一個表達(dá)式,它包含零或多個變量、常數(shù)和文本。求值的表達(dá)式的數(shù)據(jù)類型通常應(yīng)與為相應(yīng)形參定義的數(shù)據(jù)類型相匹配,并且在任何情況下,該表達(dá)式值都必須可轉(zhuǎn)換為此形參類型。

看官如果硬著頭皮看完這段引文,發(fā)現(xiàn)里面有幾個關(guān)鍵詞:參數(shù)、變量、形參、實參。本來想弄清楚參數(shù)和變量,結(jié)果又冒出另外兩個東東,更混亂了。請稍安勿躁,本來這段引文就是有點多余,但是,之所以引用,就是讓列位開闊一下眼界,在編程業(yè)界,類似的東西有很多名詞。下次聽到有人說這些,不用害怕啦,反正自己聽過了。

在Python中,沒有這么復(fù)雜。

看完上面讓人暈頭轉(zhuǎn)向的引文之后,再看下面的代碼,就會豁然開朗了。

>>> def add(x):     #x是參數(shù),準(zhǔn)確說是形參
...     a = 10      #a是變量
...     return a+x  #x就是那個形參作為變量,其本質(zhì)是要傳遞賦給這個函數(shù)的值
... 
>>> x = 3           #x是變量,只不過在函數(shù)之外
>>> add(x)          #這里的x是參數(shù),但是它由前面的變量x傳遞對象3
13
>>> add(3)          #把上面的過程合并了
13

至此,看官是否清楚了一點點。當(dāng)然,我所表述不正確之處或者理解錯誤之處,也請看官不吝賜教,小可作揖感謝。

其實沒有那么復(fù)雜。關(guān)鍵要理解函數(shù)名括號后面的東東(管它什么參呢)的作用是傳遞值。

全局變量和局部變量

下面是一段代碼,注意這段代碼中有一個函數(shù)funcx(),這個函數(shù)里面有一個變量x=9,在函數(shù)的前面也有一個變量x=2

x = 2

def funcx():
    x = 9
    print "this x is in the funcx:-->",x

funcx()
print "--------------------------"
print "this x is out of funcx:-->",x

那么,這段代碼輸出的結(jié)果是什么呢?看:

this x is in the funcx:--> 9
--------------------------
this x is out of funcx:--> 2

從輸出看出,運行funcx(),輸出了funcx()里面的變量x=9;然后執(zhí)行代碼中的最后一行,print "this x is out of funcx:-->",x

特別要關(guān)注的是,前一個x輸出的是函數(shù)內(nèi)部的變量x;后一個x輸出的是函數(shù)外面的變量x。兩個變量彼此沒有互相影響,雖然都是x。從這里看出,兩個x各自在各自的領(lǐng)域內(nèi)起到作用。

把那個只在函數(shù)體內(nèi)(某個范圍內(nèi))起作用的變量稱之為局部變量。

有局部,就有對應(yīng)的全部,在漢語中,全部變量,似乎有歧義,幸虧漢語豐富,于是又取了一個名詞:全局變量

x = 2
def funcx():
    global x    #跟上面函數(shù)的不同之處
    x = 9
    print "this x is in the funcx:-->",x

funcx()
print "--------------------------"
print "this x is out of funcx:-->",x

以上兩段代碼的不同之處在于,后者在函數(shù)內(nèi)多了一個global x,這句話的意思是在聲明x是全局變量,也就是說這個x跟函數(shù)外面的那個x同一個,接下來通過x=9將x的引用對象變成了9。所以,就出現(xiàn)了下面的結(jié)果。

this x is in the funcx:--> 9
--------------------------
this x is out of funcx:--> 9

好似全局變量能力很強悍,能夠統(tǒng)帥函數(shù)內(nèi)外。但是,要注意,這個東西要慎重使用,因為往往容易帶來變量的換亂。內(nèi)外有別,在程序中一定要注意的。

命名空間

這是一個比較不容易理解的概念,特別是對于初學(xué)者而言,似乎它很飄渺。我在維基百科中看到對它的定義,不僅定義比較好,連里面的例子都不錯。所以,抄錄下來,幫助讀者理解這個名詞。

命名空間(英語:Namespace)表示標(biāo)識符(identifier)的可見范圍。一個標(biāo)識符可在多個命名空間中定義,它在不同命名空間中的含義是互不相干的。這樣,在一個新的命名空間中可定義任何標(biāo)識符,它們不會與任何已有的標(biāo)識符發(fā)生沖突,因為已有的定義都處于其它命名空間中。

例如,設(shè)Bill是X公司的員工,工號為123,而John是Y公司的員工,工號也是123。由于兩人在不同的公司工作,可以使用相同的工號來標(biāo)識而不會造成混亂,這里每個公司就表示一個獨立的命名空間。如果兩人在同一家公司工作,其工號就不能相同了,否則在支付工資時便會發(fā)生混亂。

這一特點是使用命名空間的主要理由。在大型的計算機程序或文檔中,往往會出現(xiàn)數(shù)百或數(shù)千個標(biāo)識符。命名空間(或類似的方法,見“命名空間的模擬”一節(jié))提供一隱藏區(qū)域標(biāo)識符的機制。通過將邏輯上相關(guān)的標(biāo)識符組織成相應(yīng)的命名空間,可使整個系統(tǒng)更加模塊化。

在編程語言中,命名空間是對作用域的一種特殊的抽象,它包含了處于該作用域內(nèi)的標(biāo)識符,且本身也用一個標(biāo)識符來表示,這樣便將一系列在邏輯上相關(guān)的標(biāo)識符用一個標(biāo)識符組織了起來。許多現(xiàn)代編程語言都支持命名空間。在一些編程語言(例如C++和Python)中,命名空間本身的標(biāo)識符也屬于一個外層的命名空間,也即命名空間可以嵌套,構(gòu)成一個命名空間樹,樹根則是無名的全局命名空間。

函數(shù)和類的作用域可被視作隱式命名空間,它們和可見性、可訪問性和對象生命周期不可分割的聯(lián)系在一起。

顯然,用“命名空間”或者“作用域”這樣的名詞,就是因為有了函數(shù)(后面還會有類)之后,在函數(shù)內(nèi)外都可能有外形一樣的符號(標(biāo)識符),在python中(乃至于其它高級語言),通常就是變量,為了區(qū)分此變量非彼變量(雖然外形一樣),需要用這樣的東西來框定每個變量所對應(yīng)的值(發(fā)生作用的范圍)。

前面已經(jīng)講過,變量和對象(就是所變量所對應(yīng)的值)之間的關(guān)系是:變量類似標(biāo)簽,貼在了對象上。也就是,通過賦值語句實現(xiàn)了一個變量標(biāo)簽對應(yīng)一個數(shù)據(jù)對象(值),這種對應(yīng)關(guān)系讓你想起了什么?映射!python中唯一的映射就是dict,里面有“鍵值對”。變量和值得關(guān)系就有點像“鍵”和“值”的關(guān)系。有一個內(nèi)建函數(shù)vars,可以幫助我們研究一下這種對應(yīng)關(guān)系。

>>> x = 7
>>> scope = vars()
>>> scope['x']
7
>>> scope['x'] += 1
>>> x
8
>>> scope['x']
8

既然如此,誠如前面的全局變量和局部變量,即使是同樣一個變量名稱。但是它在不同范圍(還是用“命名空間”這個詞是不是更專業(yè)呢?)對應(yīng)不同的值。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號