Python函數基礎


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

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

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

有3個需要注意的地方:

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

如果函數體body語句只有一行,或者可以簡寫為一行,則可以寫在def的同行。例如:

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 None

關於函數參數

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

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的函數參數和返回值非常的靈活。任何類型的變量或數據結構都可以傳遞給參數,這實際上是變量賦值的過程。例如:

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM