Python函數基礎

函數聲明、調用、返回基礎

Python中使用def關鍵字來聲明函數,聲明函數的格式爲:html

def func_name(args):
    ...body...
    [return ...]

有3個須要注意的地方:python

  1. 函數名後面必須加冒號
  2. 若是函數體和def不在同一行,則必須縮進
  3. return指定函數返回值,用來結束函數
    • 但return語句是無關緊要的,若是不給return,則等價於加上了return None,即函數默認返回None結構

若是函數體body語句只有一行,或者能夠簡寫爲一行,則能夠寫在def的同行。例如:express

def myfunc(x,y,z): print(x+y+z)

函數聲明好以後,就能夠執行函數,執行函數也稱爲調用函數,方式爲func_name(args),例如:編程

myfunc(1,2,3)

函數中每每會包含一個return或多個return語句,它能夠出如今函數中的任意位置處,它用來結束函數的執行,並返回給定的值。例如:數據結構

def func(x):
    return x+5

表示返回x+5的值,返回值是一種值類型,因此能夠賦值給變量、能夠輸出、能夠操做等等。例如:閉包

print(func(3))    # 輸出返回值

a=func(4)         # 賦值給變量
print(a)

print(func(5)+3)  # 數值操做

return語句是可選的,若是函數中不指定return語句,則默認返回None,即相似於return Noneapp

關於函數參數

函數的參數其實也是變量,只不過這些變量是獨屬於函數的本地變量,函數外部沒法訪問。在函數調用的時候,會將給定的值傳遞給函數的參數,這其實是變量賦值的過程。編程語言

def myfunc(x,y,z):
    print(x,y,z)

myfunc(1,2,3)

def首先聲明好函數,而後到了myfunc(1,2,3)時,表示調用函數(執行函數),調用函數時會將給定的值1,2,3傳遞給函數的參數x,y,z,其實就是變量賦值x=1,y=2,z=3,而後使用print輸出它們。函數

因爲python是動態語言,無需先聲明變量,也無需指定變量的類型,因此python的函數參數和返回值很是的靈活。任何類型的變量或數據結構均可以傳遞給參數,這其實是變量賦值的過程。例如:3d

myfunc(1,2,3)
myfunc("abc",2,"def")
myfunc([1,2,3],4,5)

上面幾個函數調用語句中,賦值給參數的值能夠是數值類型,能夠是字符串類型,能夠是列表類型,也能夠是其它任何數據類型。

python函數的參數相比其它語言要複雜一些,意味着要靈活不少,短短一個小節的篇幅徹底無法解釋清楚,關於參數細節,詳細內容見後面的文章

函數聲明、調用的過程詳述

def用來聲明一個函數,python的函數包括函數名稱、參數、函數體、函數體中涉及到的變量、返回值。

實際上,函數名稱實際上是一個變量名,def表示將保存在某塊內存區域中的函數代碼體賦值給函數名變量。例如:

def myfunc(x,y,z):
    ...CODE...

上面表示將函數體賦值給變量名myfunc。以下圖:

既然是變量,就能夠進行輸出:

def myfunc(x):
    return x+5

print(myfunc)

輸出結果:

<function myfunc at 0x032EA6F0>

因爲python是解釋型語言,因此必須先定義函數,才能調用函數。

若是導入一個模塊文件,導入的時候會解釋、執行文件中的代碼,包括def語句,也就是說,導入文件時會先聲明好函數。

函數變量的細節

請必定理解本節內容,也許細節方面可能會有些不許確,但對於深刻理解函數來講(不只限於python語言),是很是有幫助的,特別是理解做用域規則的時候。

python是解釋性語言,讀一行解釋一行,解釋一行忘記一行。而函數是一種代碼塊,代碼塊是一個解釋單元,是一個總體。在代碼塊範圍內不會忘記讀取過的行,也不會讀一行就當即解釋一行,而是讀取完全部代碼塊內的行,而後統籌安排地進行解釋。關於這一點,在後面的文章代碼塊詳述中有很是詳細的解釋,建議一讀。

當python讀取到def所在行的時候,知道這是一個函數聲明語句,它有一個屬於本身的代碼塊範圍,因而會讀完整個代碼塊,而後解釋這個代碼塊。在這個解釋過程當中,會記錄好變量以及該變量的所屬做用域(是全局範圍內的變量仍是函數的本地變量),但必定注意,def聲明函數的過程當中不會進行變量的賦值(參數默認值除外,見下文),只有在函數調用的時候纔會進行變量賦值。換句話說,在def聲明函數的過程當中,在函數被調用以前,函數所記錄的變量一直都是變量的地址,或者通俗一點理解爲記錄變量的名稱,而不會進行變量的賦值替換

實際上,變量的明確的值會看成常量被記錄起來。如a=10的10被做爲常量,而變量a賦值給變量b時b=a,a顯然不會做爲常量。

以下函數:

x=3
def myfunc(a,b):
    c=10
    print(x,a,b,c)

myfunc(5,6)

輸出結果爲:"3 5 6 10"。

上面的函數涉及到了4個變量:a、b、c、x。其中:

  • 全局變量x
  • 本地變量a、b、c,其中本地變量a和b是函數的參數

在def的過程當中,會完完整整地記錄好這些變量以及所屬做用域,但只會記錄,不會進行變量的賦值。以下圖:

而後函數被調用,這時候纔會開始根據記錄的做用域搜索變量是否存在,是否已經賦值(非本地變量),並對須要賦值的變量賦值:

  • 查找全局變量變量x,它在全局做用域內已經賦值過了,因此只需找到這個全局變量便可
  • 查找本地變量a、b、c,它們是屬於函數myfunc的本地變量,而a和b是參數變量,因此最早對它們進行賦值a=5,b=6,而後賦值普通的本地變量c=10

如圖:

最後執行print(x,a,b,c)輸出這些變量的值。

還需注意,python是讀一行解釋一行的,在函數調用過程當中,由於c=10print()的前面,因此是先賦值c=10,再執行print,若是print在c=10前面,則先執行print,再賦值,這顯然是錯誤的,由於print()中使用了變量c,但目前尚未對其賦值。這和其它語言可能有些不一樣(特別是編譯型語言),它們可能會無視變量賦值以及變量使用的位置先後關係。

若是上面的示例中,函數myfunc調用以前,將變量x賦值爲另外一個值:

x=3
def myfunc(a,b):
    c=10
    print(x,a,b,c)

x=33
myfunc(5,6)

這時將輸出:"33 5 6 10"。由於x是全局變量,只有在函數調用的時候纔會去找到變量x對應的值,而這時全局變量的值已是33。

匿名函數lambda

匿名函數是指沒有名稱的函數,任何編程語言中,匿名函數都扮演着重要角色,它的功能很是靈活,可是匿名函數中的邏輯通常很簡單,不然直接使用命名函數更好,匿名函數經常使用於回調函數、閉包等等。

在python中使用lambda關鍵字聲明匿名函數,python中的lambda是一個表達式而不是一個語句,這意味着某些語句環境下可能沒法使用def聲明函數,但卻可使用lambda聲明匿名函數。固然,匿名函數能實現的功能,命名函數也以同樣都能實現,只不過有時候可能會比較複雜,可讀性會更差。

lambda聲明匿名函數的方式很簡單,lambda關鍵字後面跟上參數列表,而後一個冒號,冒號後跟一個表達式。

lambda argl, arg2,... argN :expression statement

lambda表達式返回一個匿名函數,這個匿名函數能夠賦值給一個變量

例如:

# 聲明匿名函數,並賦值給變量f
f = lambda x,y,z: x+y+z

print(f)

輸出結果:

<function <lambda> at 0x027EA6F0>

既然匿名函數賦值給了變量,這個函數就像是命名變量同樣,能夠經過這個變量去調用這個匿名函數。固然,它畢竟仍是匿名函數,正如上面輸出的結果中function <lambda>所示。並且,匿名函數並不是必定要賦值給變量。

# 調用匿名函數
print(f(2,3,4))  # 輸出9

匿名函數的返回值是冒號後面的表達式計算獲得的結果。對於上面的示例,它等價於return x+y+z

由於lambda是一個表達式,因此能夠寫在任何表達式能夠出現的位置處,而某些語句上下文環境中,並不能直接使用def來聲明。例如,將函數保存到一個列表中:

L=[ lambda x: x * 2,
    lambda x: x * 3,
    lambda x: x * 4 ]

print(L[0](2))
print(L[1](2))
print(L[2](2))

上面的lambda出如今列表的內部,且這裏面的匿名函數並賦值給某個變量。像def語句就沒法出如今這樣的環境中,若是真要使用def來聲明函數,並保存到列表中,只能在L的外部使用def定義,而後將函數名來保存。

def f1(x): return x * 2
def f2(x): return x * 3
def f3(x): return x * 4

L=[f1,f2,f3]

print(L[0](2))
print(L[1](2))
print(L[2](2))

看上去沒什麼問題,但函數定義的位置和列表L定義的位置可能會相差甚遠,可讀性可能會很是差。

同理的,還能夠將匿名函數保存在字典的value位置上:

key='four'
print(
    {
        'two':(lambda x: x * 2),
        'three':(lambda x: x * 3),
        'four':(lambda x: x * 4)
    }[key](2)
)

函數嵌套

函數內部能夠嵌套函數。通常來講,在函數嵌套時,內層函數會做爲外層函數的返回值(固然,並不是必須)。既然內層函數要做爲返回值,這個嵌套的內層函數更可能會是lambda匿名函數。

例如:

def f(x):
    y=10
    def g(z):
        return x+y+z
    return g

上面的函數f()中嵌套了一個g(),並返回這個g()。其實上面示例中的g()是一個閉包函數。

既然f()返回的是函數,這個函數能夠賦值給其它變量,也能夠直接調用:

# 將嵌套的函數賦值給變量myfunc
# 這時myfunc()和g()是等價的
myfunc = f(3)
print( myfunc(5) )

# 直接調用g()
print( f(3)(5) )

固然,嵌套lambda匿名函數也能夠,且更常見:

def f(x):
    y=10
    return lambda z: x+y+z

嵌套在循環內部的函數

看下面嵌套在循環內部的函數,在每一個迭代過程當中都聲明一個匿名函數,這個匿名函數返回循環控制變量i,同時將聲明的匿名函數保存到列表L中。

def f():
    L=[]
    for i in range(5):
        L.append( lambda : i )
    return L

但若是調用該函數f(),並調用保存在列表中的每一個匿名函數,會發現它們的值徹底相同,且都是循環迭代的最後一個元素值i=4

List = f()
print(List[0]())
print(List[1]())
print(List[2]())
print(List[3]())
print(List[4]())

執行結果:

4
4
4
4
4

爲何會如此?爲何循環迭代過程當中的i沒有影響到匿名函數的返回值?這是一個很是值得思考的問題,若是不理解結果,請仔細回顧前文函數變量的細節。若是仍是不理解,請閱讀Python做用域詳述

嵌套函數的做用域

此處給幾個示例,這些示例的結果對於只學過python的人來講,可能會很容易理解,但對於學過其它語言的人來講,很容易混淆出錯。

此處並不會對這些示例的結果進行解釋,由於只要理解了前文函數變量的細節,這幾個示例的結果很容易理解。

一樣,更詳細的內容參見Python做用域詳述

以下示例:

x=3
def f():
    x=4
    g()
    print("f:",x)

def g():
    print("g:",x)

f()

輸出:

g: 3
f: 4

若是在調用函數前,修改全局變量x的值:

x=3
def f():
    x=4
    g()
    print("f:",x)

def g():
    print("g:",x)

x=6
f()

輸出:

g: 6
f: 4

若是把g()的聲明放在f()的內部呢?

x=3
def f():
    x=4
    def g():
        print("g:",x)
    print("f:",x)
    x=5
    return g

f()()

輸出:

f: 4
g: 5
相關文章
相關標籤/搜索