1. 程式人生 > 其它 >python學習筆記:函式、裝飾器

python學習筆記:函式、裝飾器

技術標籤:python

函式

注:python中無函式過載

定義函式

格式:

def 函式名(引數列表):
    函式體
#函式體需要有一個相對def語句的tab縮排

注:函式名亦可以作為實參傳入函式

# 例:定義一個函式:生成10個[1,20)的隨機數並列印
def fun():
    for i in range(10):
        ra = random.randint(1, 20)
        print(ra)

fun()

# 例:定義一個函式:求兩數和並返回
def addfun(a, b):
    return a + b

print(addfun(1
, 2)) # 求可迭代資料物件的最大值 def getmax(iterable): maxi = iterable[0] for i in iterable: if i > maxi: maxi = i return maxi print(getmax([2, 3, 1, 0]))

可變引數

格式:

#形式1:
def 函式名(*引數名):# 星號後的引數名一般用 args
    函式體

#形式2:
def 函式名(**引數名):# 星號後的引數名一般用 kwargs
    函式體

可變引數必須位於引數列表末尾,且形式1和形式2最多出現一次,若兩種形式同時出現,則*

必須位於**之前
在底層進行實參與形參的匹配時,

  • 若遇到一個星號,則會將其後進行匹配的實參都裝進一個以星號後的引數名命名的元組形參中
    • 若在相應位置傳入list,tuple…等容器變數作為* 的實參,則此容器變數會被整個看作原元組的一個元素
    • 若想要是的傳入的容器中的元素作為引數的元素,應當在傳參時在其前方新增*進行拆包
    def fun(a, b, *c):
        print(a, b, c)
    
    fun(1,2)# 1 2 ()
    fun(1, 2, 3)# 1 2 (3,)
    fun(1,2,3,4)#1 2 (3, 4) 
    
    def fun(a, *c):
        print(a, c)
    
    tup =
    [3, 5] fun(0,tup) # 0 ([3, 5],) fun(0,*tup) ##0 (3, 5) # 編寫一個函式,實現求多個數據的和 def fun(*args): sum_f = 0 if len(args) > 0:# 判斷元組是否為空 for i in args: if isinstance(i,int):#判斷元素是否為整型變數 sum_f += i print(sum_f) fun(1, 2, 3)
  • 若遇到兩個星號,則會將其後進行匹配的實參都裝進一個以星號後的引數名命名的字典形參中
    • 此時進行傳參時,對應位置應當以key = value的形式傳參,
      (注意:若key為字串形式,則其不可用引號包裹)
    • 在此種情況下也可以把字典作為實參,傳入時應當在其前方新增**表示拆包為key = value的形式
    def fun(**info):
        print(info)
    
    fun()#列印  {}
    fun(name='feng', age=19)#列印{'name': 'feng', 'age': 19}
    
    dict_1 = {'type': '001', 'weight': 65} #定義一個字典變數用於作為實參傳遞
    fun(**dict_1)# 列印{'type': '001', 'weight': 65}
    
def fun(a, b, *c, **d):
    print(a, b, c, d)

fun(1, 2) #列印 1 2 () {}
fun(1, 2, 3, 4)#列印 1 2 (3, 4) {}
fun(1, 2, x=5, y=6)#列印 1 2 () {'x': 5, 'y': 6}
fun(1, 2, 3, x=5, y=6) # 列印  1 2 (3,) {'x': 5, 'y': 6}
fun(1, 2, x=5, 3) # 出錯

關鍵字引數

在引數列表中以key = value的形式出現,

def 函式名(..., key = value, ...)
   函式體

此引數在傳參時可以省略,使用value值做為預設值
當引數列表中含有多個關鍵字引數時,引數的匹配會按引數列表中的順序進行匹配
若想要給位於中後部的關鍵字引數傳參,則需要以key = value的格式完整的指明索要傳參的引數和相應值.

def fun2(a, b=100,c = 200):
    print(a + b + c)

fun2(10) #列印310
fun2(10,10) #列印220
fun2(10, c = 20) # 列印130

def fun(a, b=100, **c):
    print(a, b, c)

fun(10)  # 10 100 {}
fun(10,10,b=20)  # 出錯,在關鍵字引數與**的可變引數同時使用時,在為**引數賦值時的key值不可與其他引數名相同

函式的返回值

使用關鍵字 return

  • return 後面可以跟多個返回值,系統會將多個引數作為元素儲存到一個元組中,然後返回此元組
  • 接收時也可以使用多個變數接收,參考拆包部分
    def fun(a, b):
        return a + b, a * b
    
    x, y = fun(10, 20)
    z = fun(10, 20)
    print(x, y, z)  # 30 200 (30, 200)
    
  • return語句也可以返回內部函式,使得外部的變數可以接收函式的內部函式,從而通過此變數來呼叫函式的內部函式(閉包)
    def fun():
        str = 'aaa'
    
        def inner_fun(): #定義內部函式
            print(str)
    
        return inner_fun #在外部函式中將內部函式名作為返回值,注意不帶括號 
    
    my_fun = fun() # 變數 my_fun 接收函式fun返回的內部函式
    my_fun() # 通過變數名my_fun加上括號來實現呼叫內部函式的功能
    

全域性變數與區域性變數

宣告在函式外面的變數稱為全域性變數,在程式碼塊中定義的變數稱為區域性變數
若在函式中修改不可變的全域性變數則會報錯,而修改可變全域性變數不會報錯

str = 'aaa'#變數str為全域性變數
def fun():
    str += 'sss'#此處在函式中試圖修改全域性變數str,會報錯
    print(str)

解決:
使用關鍵字 global
在函式中,若想修改某不可變的全域性變數的值,需要在函式內部的最前方新增帶有global關鍵字的變數宣告
(global宣告要位於要修改全域性不可變變數的函式內部,即若內部函式要修改其,則宣告應置於內部函式中)

str = 'aaa'#變數str為全域性變數
def fun():
    global str #使用global關鍵字進行宣告
    str += 'sss'#此處對str的修改不會報錯
    print(str)


list_1 = [1, 2, 3, 4]#可變全域性變數
def fun():
    list_1.append(5)#在函式中不使用全域性宣告不會報錯
    print(list_1)
fun()
print(list_1)

內部函式

在函式體中巢狀定義函式,
內部函式需要在函式內部呼叫才可以執行
帶有內部函式的函式的底層處理步驟:

  1. 直譯器掃描到外部函式的定義部分,將其載入到記憶體的一塊空間中(僅是載入,而不執行),繼續掃描函式外面的其他內容.
  2. 直譯器掃描到呼叫外部函式的語句,將相應引數(如果有引數的話)傳給函式的地址空間,開始執行函式
    • 執行函式中的操作邏輯
    • 掃描到內部函式的定義部分,將其載入到記憶體的一塊空間中(並不執行內部函式).
    • 執行外部函式的其餘部分
      • 若遇到內部函式的呼叫語句,則將相應資料傳遞到內部函式的地址空間中執行內部函式,執行完畢返回到呼叫內部函式的語句處
      • 若無則順序執行餘下邏輯

特點:

  • 可以訪問外部函式的變數
  • 可以修改外部函式定義的可變變數,若要修改外部函式中的不可變變數,需要在內部函式中進行 nonlocal宣告
  • 若要修改全域性不可變變數,則應在內部函式中進行global宣告
    def fun():
        lists = [9, 6, 5, 7, 3, 4]
        print(lists)
    
        def inner_fun():#內部函式
            lists.sort()
            print(lists)
    
        inner_fun()#呼叫內部函式
    
    fun()
    
    
    def fun():
        str = 'aaa'#str為不可變變數
    
        def inner_fun():
            str += 'bbb'#在內部函式中直接修改外部函式的不可變變數會報錯
            print(str)
        inner_fun()
    
    fun()#執行時會報錯
    
    def fun():
    
        str = 'aaa'#str為不可變變數
    
        def inner_fun():
            str += 'bbb'#在內部函式中直接修改外部函式的不可變變數會報錯
            print(str)
        inner_fun()
    
    fun()#執行時會報錯
    
    def fun():
        str = 'aaa'
    
        def inner_fun():
            nonlocal str #對要修改的外部函式變數進行nonlocal宣告
            str += 'bbb'
            print(str)
    
        inner_fun() #呼叫內部函式
    
    fun()#此時可以正確執行
    
  • 系統內建函式locals() 將區域性符號表作為字典返回(將當前作用域中的變數及其值以字典的形式返回返回)
    def fun():
        str = 'aaa'
        print(locals()) #{'str': 'aaa'}
    
        def inner_fun():
            st = 'bbb'
    
        inner_fun()
    
    fun() # 列印結果{'str': 'aaa'}
    
    • 在外部呼叫內部函式:見閉包

閉包

由函式及其內部函式組成的一個整體

將內部函式名作為外部函式的返回值的形式成為閉包
可以使用一個變數接收此返回值,並通過變數名()的方式實現在外部呼叫內部函式

def fun():
    str = 'aaa'

    def inner_fun(): #定義內部函式
    print(str)

    return inner_fun #在外部函式中將內部函式名作為返回值,注意不帶括號 

my_fun = fun() # 變數 my_fun 接收函式fun返回的內部函式
my_fun() # 通過變數名my_fun加上括號來實現呼叫內部函式的功能

閉包的條件

  1. 外部函式中定義了內部函式
  2. 外部函式有返回值
  3. 返回值為內部函式名***注意:不帶括號***
  4. 內部函式要引用了外部函式的變數

閉包的作用

  1. 可以實現統計的作用
  2. 讀取其他元素的內部變數
  3. 延長作用域

閉包的應用

  • 儲存引數狀態
    如下例,變數first_fun接收了引數為1,2時的內部函式,變數second_fun接收了引數為3,4時的內部函式,當呼叫first_fun時,結果為當引數為1,2時的結果,即其儲存了引數為1,2時的狀態

    def fun(a, b):
        c = 10
    
        def inner_fun():
            print(a + b + c)
    
        return inner_fun # 返回內部函式名
    
    first_fun = fun(1, 2) # 將引數為1,2 時的內部函式賦值給變數
    second_fun = fun(3, 4) # 將引數為3,4 時的內部函式賦值給變數
    first_fun() # 列印 13
    second_fun() # 列印 17
    
  • 計數器

    def fun():
        con = [0]
    
        def inner_fun():
            con[0] += 1
            print('當前是第{}此訪問'.format(con[0]))
    
        return inner_fun
    
    counter = fun()
    counter() # 當前是第1此訪問
    counter() # 當前是第2此訪問
    counter() # 當前是第3此訪問
    
  • 閉包同級訪問
    同級的內部函式可以互相呼叫

    def fun():
        a = 100
    
        def inner_fun1():# 內部函式1
            b = 90
            s = a + b
            print(s)
    
        def inner_fun2():# 內部函式2
            inner_fun1() # 呼叫內部函式1
            print('內部函式2')
            return 'aaaa'
    
        return inner_fun2
    
    f = fun()
    s = f() # 列印 內部函式2
    print(s)# 列印 aaaa
    

裝飾器

在不修改原函式的情況下擴充原函式的功能

引入:地址引用

def func():
    print('函式func執行')

def test(f):#其引數為函式名
    print('函式test執行')
    print(f)
    f()#執行引數函式

test(func)#呼叫函式test並將函式func的函式名作為引數傳入
'''
列印結果:
函式test執行
<function func at 0x000001FA69F06310>
函式func執行
'''

裝飾器的特點

  1. 函式作為引數傳遞給另一個函式
  2. 要有閉包的特點

裝飾器的定義與使用

def my_decorate(func):#定義裝飾器,其引數為函式名
   a = 100

   def wrapper():#裝飾器的內部函式
       func()#呼叫引數函式
       print('1111')
       print('2222')

   return wrapper#返回內部函式

#使用裝飾器,在要進行裝飾的函式定義前添加註解  @裝飾器名

@my_decorate #添加註解
def my_func():#定義被裝飾函式my_func()
   print('0000')


my_func()  # 呼叫函式my_func()
'''
列印結果:
0000
1111
2222
'''

上述裝飾器為自定義函式my_func()增加了列印11112222的功能

def my_decorate(func):
    a = 100
    print('wrapper外層列印測試....')#新增輸出語句
    def wrapper():
        func()
        print('1111')
        print('2222')
    print('wrapper載入完成...')#在wrapper函式外新增輸出語句
    return wrapper

@my_decorate  # 用裝飾器修飾函式
def my_func():
    print('0000')
# 此處並未呼叫被裝飾的函式
'''
列印結果:
wrapper外層列印測試....
wrapper載入完成...
'''

裝飾器的執行:

  1. 直譯器解釋到裝飾器的定義部分,在記憶體中開闢區域載入裝飾器函式(此時載入器並不會解釋其內部的邏輯,僅僅將其載入到記憶體)
  2. 直譯器解釋到@裝飾器名處,底層自動進行以下處理
    • 將被裝飾函式名作為引數傳遞給裝飾器(相當於呼叫了裝飾器函式)
    • 系統執行裝飾器函式的內部邏輯
    • 直譯器解釋到裝飾器函式的內部函式,將其載入到記憶體的一塊區域中(裝飾器的內部函式中必定存在呼叫引數函式(即被裝飾函式)的語句,但底層並不呼叫內部函式)
    • 直譯器執行其他處理邏輯(即內部函式之後的邏輯)
    • 直譯器執行裝飾器函式的return語句(裝飾器使用了閉包的方式,因此其必定存在返回內部函式名的return語句)
    • 系統底層使用與被裝飾函式同名的變數接收裝飾器返回的內部函式名(此時在呼叫被裝飾函式的地方的地址已經變成了裝飾器內部函式的地址,而並非原函式,可通過列印被裝飾函式的函式名來驗證,其地址已經變成了裝飾器的內部函式)
  3. 呼叫被呼叫函式(相當於呼叫了裝飾器的內部函式)

帶有引數的被裝飾函式

若被裝飾函式有引數,則裝飾器的內部函式以及內部函式中進行呼叫引數函式的語句都應當帶上引數,為了使裝飾器可以適合多種引數列表的形式,可以採用兩種可變引數作為裝飾器內部函式的引數,並在其內部呼叫引數函式時將兩個可變引數進行拆包傳入.

import time

def my_decorate(func):
    a = 10

    def wrapper(*args, **kwargs):#裝飾器內部函式帶引數,為適合多種引數形式,因此採用可變引數
        print('載入中,請稍後...')
        time.sleep(2)  # 等待2秒鐘
        print('載入完成')
        func(*args,**kwargs)#呼叫引數函式時傳入引數,此處應帶上*及**進行拆包

    return wrapper

@my_decorate
def f1(n):
    print('--f1--', n)

f1(4)

@my_decorate
def f2(a, b):
    print('--f2--', a, b)

f2(4, 5)

@my_decorate
def f3(lists):
    for i in lists:
        print(i)

names = ['lily', 'lucy', 'mike']
f3(names)

@my_decorate
def f4(a, age=12):
    print('--f1--', a, age)

f4(18, age=10)

帶引數的裝飾器

若裝飾器要求帶有引數,則此裝飾器需要有三層

def outer(a): # 裝飾器第一層,帶有一個引數a,其負責接收裝飾器的引數
    def decorate(func): # 裝飾器第二層,其負責接收被裝飾函式
        def wrapper(*args,**keargs): # 裝飾器第三層.其負責接收被裝飾函式的引數
            func(*args,**keargs)
            print('裝飾'.format(a))

        return wrapper # 返回第三層

    return decorate # 返回第二層

@outer(10) # 新增裝飾器並傳入引數
def my_fun():
    print('hahaha')

my_fun()
'''
列印結果:
hahaha
裝飾10
'''

多個裝飾器修飾同一個被裝飾函式

被裝飾函式可以被多個裝飾器同時裝飾,但其裝飾順序採用就近原則,越靠近被裝飾函式的就越先進行裝飾

def decorate_1(func):#裝飾器1
    print('動作1---開始')
    def wrapper():
        func()
        print('動作1--拿筷子')

    print('act_1---結束')
    return wrapper


def decorate_2(func):#裝飾器2
    print('動作2---開始')
    def wrapper():
        func()
        print('動作2--拿饅頭')

    print('act_2---結束')
    return wrapper


@decorate_1
@decorate_2
def eat():#被裝飾函式,使用兩個裝飾器進行修飾
    print('準備吃飯...')

eat()#呼叫被裝飾函式
'''
動作2---開始
動作2---結束
動作1---開始
動作1---結束
準備吃飯...
act_2--拿饅頭
act_1--拿筷子
'''

上例中裝飾器距離被裝飾函式最近,先由其進行裝飾.

裝飾器的應用–付款(模擬)

import time

islogin = False


def login():  # 登入模組
    username = input('請輸入使用者名稱:\n')
    pw = input('請輸入密碼:\n')
    if username == 'admin' and '123456':
        return True
    else:
        return False


# 定義一個裝飾器,進行付款驗證
def login_required(func):
    def wrapper(*args, **kwargs):
        # 驗證使用者是否已經登入
        global islogin
        if islogin:
            func(*args, **kwargs)
        else:
            # 跳轉到登陸模組
            print('尚未登陸,請登入')
            islogin = login()
            print('result', islogin)

    return wrapper

@login_required
def pay(money):
    print('正在付款,付款金額為:{}'.format(money))
    print('付款中')
    time.sleep(2)
    print('付款完成')

pay(100)

匿名函式

對於邏輯較少的函式(函式體只有一兩行)可通過匿名函式的形式進行簡化.

  • 定義格式:

    lambda 引數1,引數2...:運算  # 運算結果會自動return 
    
  • 匿名函式的呼叫:
    使用一變數接收匿名函式,則此變數與匿名函式指向同一地址,可通過變數名()的形式呼叫此匿名函式,匿名函式的運算結果會自動return,因此也需要一變數接收匿名函式的返回值
    如:

    fun = lambda a, b: a + b  # 定義匿名函式並用一變數接收此函式
    
    sum=fun(1,2)  # 呼叫匿名函式
    
  • 匿名函式可以作為函式的引數

    def fun(x, y, func):  # 引數func為一函式引數
        print(x, y)
        print(func)
        a = func(x, y)
        print(a)
    
    fun(1, 2, lambda a, b: a + b)  # 將匿名函式作為引數傳入
    
  • 匿名函式與內建函式結合
    如,內建函式max()

    def max(*args, key=None): # known special case of max
        """
        max(iterable, *[, default=obj, key=func]) -> value
        max(arg1, arg2, *args, *[, key=func]) -> value
        
        With a single iterable argument, return its biggest item. The
        default keyword-only argument specifies an object to return if
        the provided iterable is empty.
        With two or more arguments, return the largest argument.
        """
        pass
    
    list2 = [{'a': 10, 'b': 20}, {'a': 13, 'b': 8}, {'a': 2, 'b': 5} # 其為元素是字典的列表,
    max_li = max(list2, key=lambda dic: dic['a'])#呼叫max()時指定器關鍵字引數key,使器在比較列表中的各個字典時按照字典中的'a'進行比較
    print(max_li)
    

    上例中列表的元素為字典型別,max()函式無法直接比較字典的大小(字典無法使用>及<進行比較),要想對其進行比較,需要使用max()函式中的關鍵字引數,指定其為字典中的可比較型別,是的函式按照此型別對字典進行比較.
    max()函式的關鍵字引數key=func,即其接收一個函式作為引數,預設值為None,在使用此引數時,可以使用匿名函式作為引數傳入