一文搞懂Python函數(匿名函數、嵌套函數、閉包、裝飾器)!

Python函數定義、匿名函數、嵌套函數、閉包、裝飾器

函數核心理解

  • 函數也是對象,可以把函數賦予變數
  • 可以把函數當作參數,傳入另一個函數中
  • 可以在函數里定義函數,函數嵌套
  • 函數的返回值也可以是函數對象,閉包

1. 函數定義

def name(param1, param2, ..., paramN):
    statements
    return/yield value # optional
  • def是可執行語句,函數直到被調用前,都是不存在的,當程式調用函數時,def語句才會創建一個新的函數對象,並賦予其名字

  • 主程式調用函數時,必須保證這個函數此前已經定義過,不然會報錯

  • 在函數內部調用其他函數時,函數間哪個申明在前、哪個在後無所謂,只要保證調用時,所需的函數都已經聲明定義

  • python不用考慮輸入的數據類型,而是將其交給具體的程式碼去判斷執行,這種行為稱為多態。

  • python函數的參數可以設定默認值,可以指定數據類型

    def name(param1 = 0, param2: int, ..., paramN):
    

函數作用

  • 減少程式碼的重複性
  • 模組化程式碼

2. 嵌套函數

2.1 作用
  • 能夠保證內部函數的隱私,內部的函數只能被外部函數所調用和訪問,不會暴露在全局作用域
  • 合理使用,可以提高程式的運行效率
2.2 函數變數作用域
  • 局部變數:函數內部定義的,只在函數內部有效,一旦執行完畢,局部變數就會被回收,無法訪問
  • 全局變數定義在整個層次上的,不能在函數內部隨意修改全局變數的值,如要修改,加global關鍵字
  • 函數內部,局部變數和全局變數同名,局部變數會覆蓋全局變數
  • 嵌套函數,內部函數可以訪問外部函數定義的變數,但無法修改,如要修改,加nonlocal關鍵字
  • 內部函數的變數和外部函數變數同名,覆蓋
MIN = 1 # 全局變數
MAX = 8
print(f"MIN={MIN},MAX={MAX}")
def f1():
    global MIN # 使用global關鍵字 修改全局變數
    MIN += 1
    a = 5  # 局部變數
    b = 1
    print(f"MIN={MIN},a={a},b={b}")
    def f2():
        MAX = 9 # 局部變數與全局變數同名,局部變數會覆蓋全局變數
        nonlocal a # 使用nonlocal關鍵字 修改外部函數的變數
        a += 1
        b = 2 # 內部函數變數與外部函數變數同名,覆蓋
        print(f"MIN={MIN},MAX={MAX},a={a},b={b}")
    f2()  # 內部函數被外部函數調用

f1()
print(f"MIN={MIN},MAX={MAX}")
MIN=1,MAX=8
MIN=2,a=5,b=1
MIN=2,MAX=9,a=6,b=2
MIN=2,MAX=8

3. 閉包

3.1 特點
  • 和嵌套函數類似,只是,外部函數返回的是一個函數,而不是一個具體的值
  • 返回的函數通常賦予一個變數,這個變數可以在後面被繼續執行調用
  • 可以簡化程式的複雜度,提高可讀性
3.2 實例
# 計算一個數的n次冪
def nth_power(exponent):
    def exponent_of(base):
        return base **exponent
    return exponent_of # 返回一個函數

# 調用函數
square = nth_power(2)
print(square)
print(square(3))
<function nth_power.<locals>.exponent_of at 0x7f3120911b90>
9

4. 裝飾器

4.1 形式和作用
  • @裝飾器函數,等價於,原函數名=裝飾器函數(原函數名)
  • 裝飾器就是通過裝飾器函數,來修改原函數的一些功能,使得原函數不需要修改,也就是擴展了原函數的功能
4.2 裝飾器函數寫法
# 簡單裝飾器
def my_decorator(func):
    def wrapper():
        print('wrapper of decorator')
        func()
    return wrapper
# 帶參數的裝飾器
def my_decorator(func):
    def wrapper(message):
        print('wrapper of decorator')
        func(message)
    return wrapper
# 通用的帶參數的裝飾器
def my_decorator(func):
    def wrapper(*args, **kwargs):
        print('wrapper of decorator')
        func(*args, **kwargs)
    return wrapper
# 保留原函數的元資訊,將原函數的元資訊,拷貝到對應的裝飾器函數里
import functools
def my_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('wrapper of decorator')
        func(*args, **kwargs)
    return wrapper

@my_decorator
def greet(message):
    print(message)
    
greet('hello world')

# 輸出
wrapper of decorator
hello world

# 類裝飾器,依賴函數__call__()
class Count:
    def __init__(self, func):
        self.func = func
        self.num_calls = 0

    def __call__(self, *args, **kwargs):
        self.num_calls += 1
        print('num of calls is: {}'.format(self.num_calls))
        return self.func(*args, **kwargs)

@Count
def example():
    print("hello world")

example()

# 輸出
num of calls is: 1
hello world
4.3 裝飾器用法實例
  • 身份認證、日誌記錄
  • 測試某些函數的執行時間
import time
import functools

def log_execution_time(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        res = func(*args, **kwargs)
        end = time.perf_counter()
        print(f"{func.__name__} took {(end - start) * 1000)} ms")
        return res
    return wrapper
    
@log_execution_time
def calculate_similarity(items):
    ...
  • 快取

    python中內置的LRU cache,@lru_cache,會快取進程中的函數參數和結果,快取滿了之後,會刪除訪問時間最早的數據

  • 工作中,二次開發,在原來的需求基礎上做優化,原邏輯不需要修改的化,只需增加新的業務場景的時候

5. 匿名函數

5.1 格式
lambda argument1, argument2,... argumentN : expression
  • 此表達式返回的是一個函數對象,用法舉例
 # 計算一個數的平方
square = lambda x: x**2 # 返回一個函數對象
a = square(3) # 調用函數
print(a)
# 9
5.2 使用原則
  • lambda是一個表達式,不是一個語句,只能寫成一行
  • 程式中需要使用一個函數完成一個簡單的共嗯那個,並且該函數只調用一次
5.3 使用方式
  • 1.用在列表內部
# 計算列表0-9的數的平方
li = [(lambda x: x*x)(x) for x in range(10)]
print(li)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
  • 2.用作某些函數的參數
# 按列表中元組的第1個元素排序
lis = [(1, 20), (3, 0), (9, 10), (2, -1)]
lis.sort(key=lambda x: x[0]) 
print(lis)
[(1, 20), (2, -1), (3, 0), (9, 10)]
# 對一個字典,根據值進行由高到低的排序
d = {"mike": 10, "luck": 2, "ben": 30}
new_li = sorted(d.items(), key=lambda x: x[1], reverse=True) # 返回的是列表嵌套元組類型
new_d = dict(new_li)
print(d,'\n', new_li, '\n', new_d, sep='')
{'mike': 10, 'luck': 2, 'ben': 30}
[('ben', 30), ('mike', 10), ('luck', 2)]
{'ben': 30, 'mike': 10, 'luck': 2}
  • 3.數據清洗中,常用lambda函數

6. python函數式編程

6.1 概念
  • 指程式碼中每一塊都是不可變的,都由純函數的形式組成
  • 純函數,是指函數本身相互獨立、互不影響,對於相同的輸入,總會有相同的輸出
6.2函數式編程的優缺點
  • 優點主要在於其純函數和不可變的特性使程式更加健壯,易於調試和測試
  • 缺點主要在於限制多,難寫
6.3 map()、filter() 和 reduce()函數
  • map(function, iterable),對序列中的每個元素都運用function這個函數,返回一個迭代器
# 對列表中的每個元素乘以2
li = [1, 2, 3, 4, 5]
new_list_1 = map(lambda x: x*2, li)
print(list(new_list_1)) # 將迭代器轉換為列表
[2, 4, 6, 8, 10]
  • filter(function, iterable),對序列中的每個元素,都使用function判斷,並返回True或者False,最後將返回True的元素組成一個新的可遍歷的集合,返回迭代器類型
# 返回列表中能夠整除2的元素
li = [1, 2, 3, 4, 5]
new_list_2 = filter(lambda x: x % 2 == 0, li)
print(list(new_list_2))
[2, 4]
  • reduce(function, iterable),規定有兩個參數,表示對序列中的每個元素以及上一次調用後的結果,運用function進行計算,最後返回的是一個單獨的數值
# 計算列表元素的乘積
from functools import reduce
li = [1, 2, 3, 4, 5]
product = reduce(lambda x, y: x * y, li)
print(product)
120