鍍金池/ 教程/ Python/ 函數(shù)(2)
標(biāo)準(zhǔn)庫 (4)
如何成為 Python 高手
標(biāo)準(zhǔn)庫 (6)
標(biāo)準(zhǔn)庫 (3)
類(2)
Pandas 使用 (2)
xml
用 tornado 做網(wǎng)站 (5)
文件(1)
練習(xí)
列表(3)
從小工到專家
除法
錯(cuò)誤和異常 (2)
函數(shù)(1)
用 tornado 做網(wǎng)站 (7)
為做網(wǎng)站而準(zhǔn)備
函數(shù)練習(xí)
標(biāo)準(zhǔn)庫 (8)
Pandas 使用 (1)
回顧 list 和 str
字典(1)
用 tornado 做網(wǎng)站 (3)
字符串(1)
函數(shù)(2)
寫一個(gè)簡單的程序
將數(shù)據(jù)存入文件
語句(5)
SQLite 數(shù)據(jù)庫
集成開發(fā)環(huán)境(IDE)
集合(1)
類(1)
用 tornado 做網(wǎng)站 (6)
用 tornado 做網(wǎng)站 (2)
自省
語句(4)
錯(cuò)誤和異常 (1)
用 tornado 做網(wǎng)站 (4)
集合(2)
列表(1)
標(biāo)準(zhǔn)庫 (1)
生成器
mysql 數(shù)據(jù)庫 (1)
第三方庫
實(shí)戰(zhàn)
運(yùn)算符
類(3)
字典(2)
語句(1)
數(shù)和四則運(yùn)算
語句(2)
文件(2)
MySQL 數(shù)據(jù)庫 (2)
電子表格
迭代器
mongodb 數(shù)據(jù)庫 (1)
特殊方法 (2)
特殊方法 (1)
字符編碼
編寫模塊
用 tornado 做網(wǎng)站 (1)
標(biāo)準(zhǔn)庫 (5)
函數(shù)(4)
類(5)
字符串(2)
關(guān)于 Python 的故事
函數(shù)(3)
字符串(4)
處理股票數(shù)據(jù)
常用數(shù)學(xué)函數(shù)和運(yùn)算優(yōu)先級
字符串(3)
為計(jì)算做準(zhǔn)備
多態(tài)和封裝
類(4)
迭代
語句(3)
錯(cuò)誤和異常 (3)
分析 Hello
Python 安裝
標(biāo)準(zhǔn)庫 (2)
列表(2)
元組

函數(shù)(2)

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

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

返回值

為了能夠說明清楚,先編寫一個(gè)函數(shù)。還記得斐波那契數(shù)列嗎?我打算定義一個(gè)能夠得到斐波那契數(shù)列的函數(shù),從而實(shí)現(xiàn)可以實(shí)現(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 的文件。在這個(gè)文件中,首先定義了一個(gè)函數(shù),名字叫做 fibs,其參數(shù)是輸入一個(gè)整數(shù)。在后面,通過 lst = fibs(10) 調(diào)用這個(gè)函數(shù)。這里參數(shù)給的是 10,就意味著要得到 n=10 的斐波那契數(shù)列。

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

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

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

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

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

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

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

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

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

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

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

我們再看看那個(gè)變量 a,到底是什么

>>> print a
None

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

>>> my_fun()
I am doing somthin.

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

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

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

函數(shù)中的文檔

“程序在大多數(shù)情況下是給人看的,只是偶爾被機(jī)器執(zhí)行以下?!彼裕瑢懗绦虮仨氁獙懽⑨?。前面已經(jīng)有過說明,如果用 # 開始,Python 就不執(zhí)行那句(Python 看不到它,但是人能看到),它就作為注釋存在。

除了這樣的一句之外,一般在每個(gè)函數(shù)名字的下面,還要寫一寫文檔,以此來說明這個(gè)函數(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

在這個(gè)函數(shù)的名稱下面,用三個(gè)引號的方式,包裹著對這個(gè)函數(shù)的說明,那個(gè)就是函數(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) 得到的也是三個(gè)引號所包裹的文檔信息。

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ù)”等名詞,到底指什么呢?

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

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

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

比較參數(shù)和變量

參數(shù)問題就算說明白了,糊涂就糊涂吧,也沒有什么關(guān)系。不過,對于變量和參數(shù),這兩個(gè)就不能算糊涂賬了。因?yàn)樗菀鬃屓撕苛恕?/p>

在數(shù)學(xué)的函數(shù)中 y = 3x + 2,那個(gè)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)用過程時(shí)傳遞給它的變量、常量和表達(dá)式。

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

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

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

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

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

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

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

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

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

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

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

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

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

全局變量和局部變量

下面是一段代碼,注意這段代碼中有一個(gè)函數(shù) funcx(),這個(gè)函數(shù)里面有一個(gè)變量 x=9,在函數(shù)的前面也有一個(gè)變量 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

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

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

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

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

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)多了一個(gè) global x,這句話的意思是在聲明 x 是全局變量,也就是說這個(gè) x 跟函數(shù)外面的那個(gè) x 同一個(gè),接下來通過 x=9 將 x 的引用對象變成了 9。所以,就出現(xiàn)了下面的結(jié)果。

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

好似全局變量能力很強(qiáng)悍,能夠統(tǒng)帥函數(shù)內(nèi)外。但是,要注意,這個(gè)東西要慎重使用,因?yàn)橥菀讕碜兞康膿Q亂。內(nèi)外有別,在程序中一定要注意的。

命名空間

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

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

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

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

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

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

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

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

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

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


總目錄   |   上節(jié):函數(shù)(1)   |   下節(jié):函數(shù)(3)

如果你認(rèn)為有必要打賞我,請通過支付寶:qiwsir@126.com,不勝感激。