一文掌握Python函數用法

本文通過理論+代碼的方式介紹了Python函數的相關知識:包括:函數的參數、參數傳遞方式、參數解包、返回值、文檔字符串、作用域、命名空間、遞歸、高階函數、匿名函數、閉包、裝飾器。在開發過程中或者看別人源碼的過程中,這些知識是非常有用和經常遇到的,因此掌握這些知識很重要。

系列文章:

【Python 基礎】一文補齊Python基礎知識
【趣學Python:B站四大惡人】一文掌握列表、元組、字典、集合



1. 函數

1.1 函數簡介(function)

  • 函數也是一個對象(object)
  • 對象是內存中專門用來存儲數據的一塊區域
  • fn是函數對象 ;fn()調用函數;例如:print 是函數對象 print() 調用函數;
  • 函數可以用來保存一些可執行的代碼,並且可以在需要時,對這些語句進行多次的調用;
  1. 創建函數:
def 函數名([形參1,形參2,...形參n]) :
    代碼塊
- 函數名必須要符號標識符的規範;
    (可以包含字母、數字、下劃線、但是不能以數字開頭)  
- 函數中保存的代碼不會立即執行,需要調用函數代碼纔會執行;
  1. 調用函數:函數對象()

1.2 函數的參數

在定義函數時,可以在函數名後的()中定義數量不等的形參,多個形參之間使用,隔開;

1.2.1 形參和實參

  • 形參(形式參數),定義形參就相當於在函數內部聲明瞭變量,但是並不賦值;
  • 實參(實際參數);如果函數定義時,指定了形參,那麼在調用函數時也必須傳遞實參;實參將會賦值給對應的形參,簡單來說,有幾個形參就得傳幾個實參。
# 定義函數時,指定形參
def fn(a, b) :
    print(a,"+",b,"=",a + b)

# 調用函數時,傳遞實參
fn(10,20)
fn(123,456)

實參的傳遞方式:

def mul(a,b,c):
    print(a*b*c)
  • 位置參數;位置參數就是將對應位置的實參複製給對應位置的形參;第一個實參賦值給第一個形參,第二個實參賦值給第二個形參 ;fn(1 , 2 , 3)
  • 關鍵字參數;關鍵字參數,可以不按照形參定義的順序去傳遞,而直接根據參數名去傳遞參數:fn(b=1 , c=2 , a=3)
  • 位置參數和關鍵字參數可以混合使用;混合使用關鍵字和位置參數時,必須將位置參數寫到前面:fn(1,c=30)
  • 函數在調用時,解析器不會檢查實參的類型;實參可以傳遞任意類型的對象
  • 如果形參執行的是一個對象,當我們通過形參去修改對象時,會影響到所有指向該對象的變量;
def fn4(a):
    a[0] = 30
    print('a =',a,id(a))
c = 10   
c = [1,2,3] 
fn4(c)
# 不改變對象
fn4(c.copy()) # or fn4(c[:])

1.2.2 不定長參數

# 定義一個函數,可以求任意個數字的和
def sum(*nums):
    # 定義一個變量,來保存結果
    result = 0
    # 遍歷元組,並將元組中的數進行累加
    for n in nums :
        result += n
    print(result)
  • 在定義函數時,可以在形參前邊加上一個*,這樣這個形參將會獲取到所有的實參;它將會將所有的實參保存到一個元組中。
  • *a會接受所有的位置實參,並且會將這些實參統一保存到一個元組中(裝包)
def fn(*a):
    print("a =",a,type(a))
fn()

'''
輸出: 
a = () <class 'tuple'>
'''
  • 帶星號的形參只能有一個;帶星號的參數,可以和其他參數配合使用;第一個參數給a,第二個參數給b,剩下的都保存到c的元組中;
def fn2(a,b,*c):
    print('a =',a)
    print('b =',b)
    print('c =',c)
fn2(1,2,3,4,5,6)

'''
輸出:
a = 1
b = 2
c = (3, 4, 5, 6)
'''
  • 可變參數不是必須寫在最後,但是注意,*的參數後的所有參數,必須以關鍵字參數的形式傳遞;第一個參數給a,剩下的位置參數給b的元組,c必須使用關鍵字參數
def fn3(a,*b,c):
    print('a =',a)
    print('b =',b)
    print('c =',c)
fn3(1,2,3,4,5,c=6)

'''
輸出:
a = 1
b = (2, 3, 4, 5)
c = 6
'''
  • 所有的位置參數都給a,b和c必須使用關鍵字參數;
def fn4(*a,b,c):
    print('a =',a)
    print('b =',b)
    print('c =',c)
fn4(1,2,3,4,b=5,c=6)

'''
輸出:
a = (1, 2, 3, 4)
b = 5
c = 6
'''
  • 如果在形參的開頭直接寫一個*,則要求所有的參數必須以關鍵字參數的形式傳遞
def fn5(*,a,b,c):
    print('a =',a)
    print('b =',b)
    print('c =',c)
fn5(a=4,b=5,c=6)

'''
輸出:
a = 4
b = 5
c = 6
'''
  • *形參只能接收位置參數,而不能接收關鍵字參數;
  • **形參可以接收其他的關鍵字參數,它會將這些參數統一保存到一個字典中;字典的key就是參數的名字,字典的value就是參數的值;**形參只能有一個,並且必須寫在所有參數的最後
def fn6(b,c,**a) :
    print('a =',a,type(a))
    print('b =',b)
    print('c =',c)

fn6(b=1,d=2,c=3,e=10,f=20)

'''
輸出:
a = {'d': 2, 'e': 10, 'f': 20} <class 'dict'>
b = 1
c = 3
'''

1.2.3 參數解包

  • 傳遞實參時,也可以在序列類型的參數前添加星號,這樣他會自動將序列中的元素依次作爲參數傳遞;這裏要求序列中元素的個數必須和形參的個數的一致
def fn7(a,b,c):
    print('a =',a)
    print('b =',b)
    print('c =',c)

# 創建一個元組
t = (10,20,30)
fn7(t[0],t[1],t[2])

# 創建一個列表
s = [10,20,30]
fn7(*s)

# 創建一個字典
d = {'a':10,'b':20,'c':30}
fn7(**d)

'''
輸出:
a = 10
b = 20
c = 30
'''

1.2.3 返回值

  • 返回值,返回值就是函數執行以後返回的結果;
  • 可以通過 return 來指定函數的返回值;
  • 可以之間使用函數的返回值,也可以通過一個變量來接收函數的返回值;
  • return 後邊可以跟任意的對象,返回值甚至可以是一個函數;
def fn():
    def fn2() :
        print('hello')
    return fn2 # 返回值也可以是一個函數
r = fn() # 這個函數的執行結果就是它的返回值
  • 如果僅僅寫一個return 或者 不寫return,則相當於return None ;
  • break 用來退出當前循環;continue 用來跳過當次循環;return 用來結束函數;
def fn4() :
    for i in range(5):
        if i == 3 :
            # break 用來退出當前循環
            # continue 用來跳過當次循環
            return # return 用來結束函數
        print(i)
    print('循環執行完畢!')
'''
break執行結果:0,1,2,循環執行完畢!
continue執行結果:0,1,2,4,循環執行完畢!
return執行結果:0,1,2
'''
def fn2() :
	a = 10
    return 
  • 在函數中,return後的代碼都不會執行,return 一旦執行函數自動結束;
    參數解包:
def sum(*nums):
    # 定義一個變量,來保存結果
    result = 0
    # 遍歷元組,並將元組中的數進行累加
    for n in nums :
        result += n
    return result

r = sum(1,2,3)
print(r)
print(r + 4)
def fn5():
    return 10

fn5fn5() 的區別:

  • fn5是函數對象,打印fn5實際是在打印函數對象;
  • fn5()是在調用函數,打印fn5()實際上是在打印fn5()函數的返回值;
    print(fn5) 輸出<function fn5 at 0x05771BB8>;
    print(fn5()) 輸出10;

1.3 文檔字符串(doc str)

  • help()是Python中的內置函數;

  • 通過help()函數可以查詢python中的函數的用法;

  • 語法:help(函數對象)

  • help(print) 獲取print()函數的使用說明;

  • 文檔字符串(doc str)

  • 在定義函數時,可以在函數內部編寫文檔字符串,文檔字符串就是函數的說明;

  • 當我們編寫了文檔字符串時,就可以通過 help() 函數來查看函數的說明;

  • 文檔字符串非常簡單,其實直接在函數的第一行寫一個字符串就是文檔字符串;

  • -> int 表示函數返回值是int類型;

def fn(a:int,b:bool,c:str='hello') -> int:
    '''
    這是一個文檔字符串的示例

    函數的作用:。。。。。
    函數的參數:
        a,作用,類型,默認值。。。。
        b,作用,類型,默認值。。。。
        c,作用,類型,默認值。。。。
    '''
    return 10

help(fn)

1.4 作用域與命名空間

1.4.1 作用域

  • 作用域(scope):指變量生效的區域;在Python中一共有兩種作用域;

全局作用域

  • 全局作用域在程序執行時創建,在程序執行結束時銷燬;
  • 所有函數以外的區域都是全局作用域;
  • 在全局作用域中定義的變量,都屬於全局變量,全局變量可以在程序的任意位置被訪問;

函數作用域

  • 函數作用域在函數調用時創建,在調用結束時銷燬;
  • 函數每調用一次就會產生一個新的函數作用域;
  • 在函數作用域中定義的變量,都是局部變量,它只能在函數內部被訪問;

變量的查找

  • 當我們使用變量時,會優先在當前作用域中尋找該變量,如果有則使用;如果沒有則繼續去上一級作用域中尋找,如果有則使用,如果依然沒有則繼續去上一級作用域中尋找,以此類推,直到找到全局作用域,依然沒有找到,則會拋出異常NameError: name 'a' is not defined
def fn2():
	a = 10
    def fn3():
        print('fn3中:','a =',a)
    fn3()

fn2()

'''
輸出:
10
'''    

在函數中爲變量賦值時,默認都是爲局部變量賦值;如果希望在函數內部修改全局變量,則需要使用 global 關鍵字,來聲明變量;

def fn():
    global a # 聲明在函數內部的使用a是全局變量,此時再去修改a時,就是在修改全局的a
    a = 10 # 修改全局變量
    print('函數內部:','a =',a)
fn()
print('函數外部:','a =',a)

1.4.2 命名空間

命名空間(namespace)

  • 命名空間指的是變量存儲的位置,每一個變量都需要存儲到指定的命名空間當中;
  • 每一個作用域都會有一個它對應的命名空間;
  • 全局命名空間,用來保存全局變量。函數命名空間用來保存函數中的變量;
  • 命名空間實際上就是一個字典,是一個專門用來存儲變量的字典;

locals()用來獲取當前作用域的命名空間

  • 如果在全局作用域中調用 locals() 則獲取全局命名空間;如果在函數作用域中調用locals()則獲取函數命名空間;返回的是一個字典;scope = locals() 獲取當前命名空間;
  • 在函數中可以獲取全局命名空間,但在全局中無法獲取函數的命名空間;
def fn4():
    a = 10
    # scope = locals() # 在函數內部調用locals()會獲取到函數的命名空間
    # scope['b'] = 20 # 可以通過scope來操作函數的命名空間,但是也是不建議這麼做

    # globals() 函數可以用來在任意位置獲取全局命名空間
    global_scope = globals()
    # print(global_scope['a'])
    global_scope['a'] = 30
    # print(scope)

fn4()    

1.5 遞歸

# 嘗試求10的階乘(10!)
創建一個變量保存結果
n = 10
for i in range(1,10):
    n *= i

print(n)

創建一個函數,可以用來求任意數的階乘:

def factorial(n):
    '''
        該函數用來求任意數的階乘
        參數:n 要求階乘的數字
    '''
    # 創建一個變量,來保存結果
    result = n
    for i in range(1,n):
        result *= i
    return result    

遞歸式的函數

  • 無窮遞歸,如果這個函數被調用,程序的內存會溢出,效果類似於死循環
def fn():
    fn()
fn()
  • 遞歸是解決問題的一種方式,和循環很像。它的整體思想是,將一個大問題分解爲一個個的小問題,直到問題無法分解時,再去解決問題。

遞歸式函數的兩個要件:
1.基線條件

  • 問題可以被分解爲的最小問題,當滿足基線條件時,遞歸就不再執行了;

2.遞歸條件

  • 將問題繼續分解的條件;

使用遞歸重寫計算任意階乘的函數:

def factorial(n):
    '''
        該函數用來求任意數的階乘
        參數:n 要求階乘的數字
    '''
    # 基線條件;判斷n是否爲1,如果爲1則此時不能再繼續遞歸
    if n == 1 :
        # 1的階乘就是1,直接返回1
        return 1

    # 遞歸條件    
    return n * factorial(n-1)
print(factorial(10))
  • 遞歸和循環類似,基本是可以互相代替的;
  • 循環編寫起來比較容易,閱讀起來稍難;遞歸編寫起來難,但是方便閱讀。
'''
 練習
   創建一個函數 power 來爲任意數字做冪運算 n ** i
   10 ** 5 = 10 * 10 ** 4
   10 ** 4 = 10 * 10 ** 3
   ...
   10 ** 1 = 10
   '''
def power(n , i):
    '''
        power()用來爲任意的數字做冪運算
        參數:
            n 要做冪運算的數字
            i 做冪運算的次數
    '''
    # 基線條件
    if i == 1:
        # 求1次冪
        return n
    # 遞歸條件
    return n * power(n , i-1)
    
print(power(8,6))
print(8**6)    

創建一個函數,用來檢查一個任意的字符串是否是迴文字符串,如果是返回True,否則返回False。

 ''' 

  迴文字符串,字符串從前往後念和從後往前念是一樣的
      abcba
  abcdefgfedcba
  先檢查第一個字符和最後一個字符是否一致,如果不一致則不是迴文字符串
      如果一致,則看剩餘的部分是否是迴文字符串
  檢查 abcdefgfedcba 是不是迴文
  檢查 bcdefgfedcb 是不是迴文
  檢查 cdefgfedc 是不是迴文
  檢查 defgfed 是不是迴文
  檢查 efgfe 是不是迴文
  檢查 fgf 是不是迴文
  檢查 g 是不是迴文
  '''
def hui_wen(s):
    '''
        該函數用來檢查指定的字符串是否迴文字符串,如果是返回True,否則返回False
        參數:
            s:就是要檢查的字符串
    '''
    # 基線條件
    if len(s) < 2 :
        # 字符串的長度小於2,則字符串一定是迴文
        return True
    elif s[0] != s[-1]:
        # 第一個字符和最後一個字符不相等,不是迴文字符串
        return False    
    # 遞歸條件    
    return hui_wen(s[1:-1])

print(hui_wen('abcdefgfedcba'))     

1.6 高階函數

  • 接收函數作爲參數,或者將函數作爲返回值的函數是高階函數;
  • 當使用一個函數作爲參數時,實際上是將指定的代碼傳遞進了目標函數;
# 創建一個列表
l = [1,2,3,4,5,6,7,8,9,10]

# 定義一個函數,用來檢查一個任意的數字是否是偶數
def fn2(i) :
    if i % 2 == 0 :
        return True
    return False    

# 這個函數用來檢查指定的數字是否大於5
def fn3(i):
    if i > 5 :
        return True    
    return False

def fn(func , lst) :
    '''
        fn()函數可以將指定列表中的所有偶數獲取出來,並保存到一個新列表中返回
        參數:
            lst:要進行篩選的列表
    '''
    # 創建一個新列表
    new_list = []

    # 對列表進行篩選
    for n in lst :
        # 判斷n的奇偶
        if func(n) :
            new_list.append(n)

    # 返回新列表
    return new_list

def fn4(i):
    return i % 3 == 0
    
print(fn(fn4 , l))

1.6.1 filter()

  • filter() 可以從序列中過濾出符合條件的元素,保存到一個新的序列中
    參數:
    1.函數,根據該函數來過濾序列(可迭代的結構)
    2.需要過濾的序列(可迭代的結構)
    返回值:過濾後的新序列(可迭代的結構)
r = filter(fn4, 1)
print(r)
  • fn4 是作爲參數傳遞進filter()函數中;fn4實際上只有一個作用,就是作爲filter()的參數;filter() 調用完畢以後,fn4 就已經沒用;

1.6.2 lambda 函數表達式 (語法糖)

  • lambda函數表達式專門用來創建一些簡單的函數,他是函數創建的又一種方式;
  • 語法:lambda 參數列表 : 返回值
  • 匿名函數一般都是作爲參數使用,其他地方一般不會使用;
r = filter(lambda i : i > 5 , l)
print(list(r))

1.6.3 map()

  • map() 函數可以對可跌倒對象中的所有元素做指定的操作,然後將其添加到一個新的對象中返回;
l = [1,2,3,4,5,6,7,8,9,10]
r = map(lambda i : i ** 2 , l)
print(list(r))

1.6.4 sort()

  • 該方法用來對列表中的元素進行排序;
  • sort()方法默認是直接比較列表中的元素的大小;
    sort()可以接收一個關鍵字參數key=key=需要一個函數作爲參數,當設置了函數作爲參數,每次都會以列表中的一個元素作爲參數來調用函數,並且使用函數的返回值來比較元素的大小。
l = ['bb','aaaa','c','ddddddddd','fff']
l.sort(key=len)
print(l)
# 輸出:['c', 'bb', 'fff', 'aaaa', 'ddddddddd']

l = [2,5,'1',3,'6','4']
l.sort(key=int)
print(l)
# 輸出:['1', 2, 3, '4', 5, '6']

1.6.5 sorted()

  • 這個函數和sort()的用法基本一致,但是sorted()可以對任意的序列進行排序;
  • 並且使用sorted()排序不會影響原來的對象,而是返回一個新對象
l = [2,5,'1',3,'6','4']
print('排序前:',l)
print(sorted(l,key=int))
print('排序後:',l)
'''
輸出:
排序前: [2, 5, '1', 3, '6', '4']
['1', 2, 3, '4', 5, '6']
排序後: [2, 5, '1', 3, '6', '4']
'''

1.6.6 閉包

  • 將函數作爲返回值返回,也是一種高階函數;這種高階函數我們也稱爲叫做閉包
  • 通過閉包可以創建一些只有當前函數能訪問的變量;也可以將一些私有的數據藏到的閉包中;
def fn():
    a = 10
    # 函數內部再定義一個函數
    def inner():
        print('我是fn2' , a)
    # 將內部函數 inner作爲返回值返回   
    return inner
r = fn()
  • r 是一個函數,是調用fn()後返回的函數;
  • 這個函數只在fn()內部定義,並不是全局函數;
  • 所以這個函數總是能訪問到fn()函數內的變量;

形成閉包的要件:
① 函數嵌套;
② 將內部函數作爲返回值返回;
③ 內部函數必須要使用到外部函數的變量;

def make_averager():
    # 創建一個列表,用來保存數值
    nums = []

    # 創建一個函數,用來計算平均值
    def averager(n) :
        # 將n添加到列表中
        nums.append(n)
        # 求平均值
        return sum(nums)/len(nums)

    return averager

averager = make_averager()

print(averager(10)) # 輸出:10.0
print(averager(20)) # 輸出:15.0
print(averager(30)) # 輸出:20.0
print(averager(40)) # 輸出:25.0

1.7 裝飾器

# 創建幾個函數
def fn():
    print('我是fn函數....')
    
def add(a , b):
    '''
        求任意兩個數的和
    '''
    r = a + b
    return r

def mul(a , b):
    '''
        求任意兩個數的積
    '''
    r = a * b
    return r  

需求:希望函數可以在計算前,打印開始計算,計算結束後打印計算完畢。

我們可以直接通過修改函數中的代碼來完成這個需求,但是會產生以下一些問題:
1.如果要修改的函數過多,修改起來會比較麻煩;
2.並且不方便後期的維護;
3.並且這樣做會違反開閉原則(OCP):
  程序的設計,要求開發對程序的擴展,要關閉對程序的修改;

爲了解決這個問題,我們創建一個函數,讓這個函數可以自動的幫助我們生產函數;

def begin_end(old):
    '''
        用來對其他函數進行擴展,使其他函數可以在執行前打印開始執行,執行後打印執行結束

        參數:
            old 要擴展的函數對象
    '''
    # 創建一個新函數
    def new_function(*args , **kwargs):
        print('開始執行~~~~')
        # 調用被擴展的函數
        result = old(*args , **kwargs)
        print('執行結束~~~~')
        # 返回函數的執行結果
        return result

    # 返回新函數        
    return new_function

  • *args接收所有位置參數;
  • **args接收所有的關鍵字參數;
  • 函數定義def new_function(*args , **kwargs)中的兩個參數是把傳入的參數裝包成一個元組(*args)或一個字典(**kwargs);
  • result = old(*args , **kwargs)中的兩個參數,*args將元組拆包成位置參數傳入函數,**kwargs將字典拆包成關鍵字參數傳入函數。

調用:

f = begin_end(fn)
f2 = begin_end(add)
f3 = begin_end(mul)

r = f()

print('第一次輸出:\n',r)
r = f2(123,456)
print('第二次輸出:\n',r)
r = f3(123,456)
print('第三次輸出:\n',r)

'''
輸出:
開始執行~~~~
我是fn函數....
執行結束~~~~
第一次輸出:
 None
開始執行~~~~
執行結束~~~~
第二次輸出:
 579
開始執行~~~~
執行結束~~~~
第三次輸出:
 56088
'''

begin_end()這種函數我們就稱它爲裝飾器

  • 通過裝飾器,可以在不修改原來函數的情況下來對函數進行擴展;
  • 在開發中,通過裝飾器來擴展函數的功能
  • 在定義函數時,可以通過@裝飾器,來使用指定的裝飾器,來裝飾當前的函數

裝飾器的標準用法:

@begin_end
def say_hello():
    print('大家好~~~')

say_hello()

'''
輸出:
大家好~~~
'''
  • 可以同時爲一個函數指定多個裝飾器,這樣函數將會按照從內向外的順序被裝飾
def fn3(old):
    '''
        用來對其他函數進行擴展,使其他函數可以在執行前打印開始執行,執行後打印執行結束
        參數:
            old 要擴展的函數對象
    '''
    # 創建一個新函數
    def new_function(*args , **kwargs):
        print('fn3裝飾~開始執行~~~~')
        # 調用被擴展的函數
        result = old(*args , **kwargs)
        print('fn3裝飾~執行結束~~~~')
        # 返回函數的執行結果
        return result

    # 返回新函數        
    return new_function

@fn3
@begin_end
def say_hello():
    print('大家好~~~')
    
say_hello()

'''
輸出:
fn3裝飾~開始執行~~~~
開始執行~~~~
大家好~~~
執行結束~~~~
fn3裝飾~執行結束~~~~
'''

交換裝飾器執行順序

@begin_end
@fn3
def say_hello():
    print('大家好~~~')
    
say_hello()
'''
輸出:
開始執行~~~~
fn3裝飾~開始執行~~~~
大家好~~~
fn3裝飾~執行結束~~~~
執行結束~~~~
'''

參考課程:https://edu.aliyun.com/course/1782

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