函數的特殊使用方式

来源:https://www.cnblogs.com/sibide/archive/2023/01/01/17018461.html
-Advertisement-
Play Games

JZ76 刪除鏈表中重覆的結點 題目 在一個排序的鏈表中,存在重覆的結點,請刪除該鏈表中重覆的結點,重覆的結點不保留,返回鏈表頭指針。 例如,鏈表 1->2->3->3->4->4->5 處理後為 1->2->5 方法1 哈希表進行刪除 思路 演算法實現 LinkedHashMap實現順序插入,不過查 ...


5.4 函數的特殊使用方式

5.4.1 匿名函數

所謂匿名函數,即不再使用def語句這樣標準形式定義的函數。Python中可以使用lambda關鍵字來創建匿名函數。用lambda創建的匿名函數的函數體比def定義的函數體要簡單。語法如下:

lambda [參數1[,參數2],....參數n]]:表達式

lam_sum = lambda arg1, arg2: arg1 + arg2
print(lam_sum(10, 20))

30

上述代碼中,第一行定義了一個lambda函數,執行兩個數的和運算,並且把該lambda函數命名為lam_sum。然後通過lam_sum()函數實現求和的功能。
Lambda創建的匿名函數中只能封裝有限的邏輯進去。
lambda函數擁有自己的命名空間,且不能訪問自有參數列表之外或全局命名空間里的參數。
實際上,一般在使用匿名函數時是不會再為創建的匿名函數命名的。因為這樣失去了匿名函數的簡便性。在有些場景是需要傳入函數,需要的邏輯並不是很複雜。但是又不想再創建一個,這個時候就可以直接使用匿名函數了。如下:

print(list(map(lambda x: x * x, [1, 2, 3, 4, 5])))

[1, 4, 9, 16, 25]

5.4.2 遞歸調用

在Python定義函數時,函數體中可以調用其他函數,甚至可以調用自己。這種自己調用自己的方式叫做遞歸調用。下麵是一個遞歸式函數定義:

def recursion():
return recursion()

顯然,對於上面定義的函數,如果你運行它,你將發現運行一段時間後,這個程式崩潰了(引發異常)。
從理論上說,這個程式將不斷運行下去,但每次調用函數時,都將消耗一些記憶體。因此函數調用次數達到一定的程度(且之前的函數調用未返回)後,將耗盡所有的記憶體空間,導致程式終止並顯示錯誤消息“超過最大遞歸深度(maximum recursion depth exceeded,預設最大為1000次)”。
可以通過以下代碼修改最大遞歸深度:

import sys
sys.setrecursionlimit(99999)

這個函數中的遞歸稱為無窮遞歸(就像以 while True 打頭且不包含 break 和 return 語句的迴圈被稱為無限迴圈一樣),因為它從理論上說永遠不會結束。你想要的是能對你有所幫助的遞歸函數,這樣的遞歸函數通常包含下麵兩部分。
基線條件:滿足這種條件時函數將直接返回一個值。
遞歸條件:包含一個或多個調用,這些調用旨在解決問題的一部分。
這裡的關鍵是,通過將問題分解為較小的部分,可避免遞歸沒完沒了,因為問題終將被分解成基線條件可以解決的最小問題。
那麼如何讓函數調用自身呢?這沒有看起來那麼難懂。前面說過,每次調用函數時,都將為此創建一個新的命名空間。這意味著函數調用自身時,是兩個不同的函數[更準確地說,是不同版本(即命名空間不同)的同一個函數]在交流。你可將此視為兩個屬於相同物種的動物在彼此交流。
遞歸示例1:通過遞歸的方式求一個數的階乘

def factorial(p_int=0):
if p_int == 0:  # 基線條件
	    return 1
else:  #  遞歸條件
return p_int * factorial(p_int - 1)


print(factorial(10))

3628800

遞歸示例2:通過遞歸的方式求冪

def power(x, n):
    return 1 if n == 0 else x * power(x, n - 1)


print(power(2, 10))

1024

遞歸示例3:通過遞歸的方式解決漢諾塔問題

def move(n, a='A', b='B', c='C'):
    if n == 1:
        print('移動', a, '-->', c)
    else:
        move(n - 1, a, c, b)
        move(1, a, b, c)
        move(n - 1, b, a, c)


move(4)

移動 A --> B
移動 A --> C
移動 B --> C
移動 A --> B
移動 C --> A
移動 C --> B
移動 A --> B
移動 A --> C
移動 B --> C
移動 B --> A
移動 C --> A
移動 B --> C
移動 A --> B
移動 A --> C
移動 B --> C

在某些特殊的問題中,如果通過普通的迴圈方式雖然也可以實現,但在使用了遞歸的方式後代碼更加簡單。邏輯也更加清楚。
理論上,所有的遞歸函數都可以寫成迴圈的方式,但迴圈的邏輯不如遞歸清晰。
使用遞歸函數需要註意防止棧溢出。在電腦中,函數調用是通過棧(stack)這種數據結構實現的,每當進入一個函數調用,棧就會加一層棧幀,每當函數返回,棧就會減一層棧幀。由於棧的大小不是無限的,所以,遞歸調用的次數過多會導致棧溢出。

5.4.3 偏函數

參考:偏函數
介紹函數參數的時候,我們講到,通過設定參數的預設值,可以降低函數調用的難度。而偏函數也可以做到這一點。
int()函數可以把字元串轉換為整數,當僅傳入字元串時,int()函數預設按十進位轉換:
>>> int('12345')

12345

但int()函數還提供額外的base參數,預設值為10。如果傳入base參數,就可以做N進位的轉換:
>>> int('12345', base=8)

5349

>>> int('12345', 16)

74565

假設要轉換大量的二進位字元串,每次都傳入int(x, base=2)非常麻煩,於是,我們想到,可以定義一個int2()的函數,預設把base=2傳進去:

def int2(x, base=2):
    return int(x, base)

這樣,我們轉換二進位就非常方便了:

>>> int2('1000000')

64

>>> int2('1010101')

85

functools.partial就是幫助我們創建一個偏函數的,不需要我們自己定義int2(),可以直接使用下麵的代碼創建一個新的函數int2:
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')

64

>>> int2('1010101')

85

所以,簡單總結functools.partial的作用就是,把一個函數的某些參數給固定住(也就是設置預設值),返回一個新的函數,調用這個新函數會更簡單。
註意到上面的新的int2函數,僅僅是把base參數重新設定預設值為2,但也可以在函數調用時傳入其他值:
>>> int2('1000000', base=10)

1000000

最後,創建偏函數時,實際上可以接收函數對象、*args和**kw這3個參數,當傳入:
>>> int2 = functools.partial(int, base=2)
實際上固定了int()函數的關鍵字參數base,也就是:

>>> int2('10010')
相當於:
>>> kw = { 'base': 2 }
>>> int('10010', **kw)
當傳入:
>>> max2 = functools.partial(max, 10)
實際上會把10作為args的一部分自動加到左邊,也就是:
>>> max2(5, 6, 7)
相當於:
>>> args = (10, 5, 6, 7)
>>> max(
args)

10

當函數的參數個數太多,需要簡化時,使用functools.partial可以創建一個新的函數,這個新函數可以固定住原函數的部分參數,從而在調用時更簡單。

5.4.4 閉包

閉包就是一種函數的嵌套,首先定義了一個函數,稱之為外部函數。在這個外部函數體中又定義了一個內部函數,並且這個內部函數體中使用到了外部函數的變數。外部函數最後return了內部函數。那麼這個外部函數以及內部函數就構成了一個特殊的對象,稱之為閉包。
閉包避免了使用全局變數,使得局部變數在函數外被訪問成為可能,相比較面向對象,不用繼承那麼多的額外方法,閉包占用了更少的空間。
閉包示例

a = 1


def out_fun(b):
    c = 3

    def in_fun(d):
        print(a + b + c + d)

    return in_fun


infun = out_fun(2)
infun(4)

10

可以看到,在內部函數in_fun中訪問到了全局變數a、外部函數out_fun的局部變數c以及參數b。

5.4.4.2 裝飾器

裝飾器(decorator)的本質:函數閉包(function closure)的語法糖(Syntactic sugar)。通過給函數裝飾,可以加強函數的功能或者增加原本函數沒有的功能。
裝飾器在第一次調用被裝飾函數時進行增強,並且只增強一次。
讓我們先從一個簡單的函數開始吧。假設現在有一個函數,用來計算從1到100累加之和,並輸出結果。為了避免計算太快,我們在使用迴圈累加時設置等待0.01秒,函數定義如下:

def mysum1():
    from time import sleep
    total = 0
    for _ in range(101):
        sleep(0.01)
        total += _
    print(total)

mysum1()

5050

此時,如果我們想要知道調用這個函數執行一共花了多少時間,我們可以在執行前後獲取時間再經過計算得到,也可以通過改造函數,在函數內部函數體前後獲取時間計算得到。但是這兩種方法都比較麻煩,尤其是第二種方法,需要侵入式修改函數代碼。
這個時候就可以通過為函數添加裝飾器來實現了。下麵是創建裝飾器的一般方法:
裝飾器的簡單定義

def decorator1(func):

    def inner():
        print('在這裡執行被裝飾函數執行前的增強操作')

        func()  # 執行被裝飾的函數

        print('在這裡執行被裝飾函數執行前的增強操作')

    return inner

從上面可以看出,裝飾器也是一個函數。只不過裝飾器接收的參數是被裝飾的函數。然後再裝飾器內部定義一個函數,該內部函數體中執行要增強的操作代碼以及執行被裝飾的函數。最後再return該內部函數。
接下來是用裝飾器來對某個函數進行裝飾。我們以上面定義的mysum1函數來進行裝飾:
裝飾器的使用

@decorator1
def mysum1():
    from time import sleep
    total = 0
    for _ in range(101):
        sleep(0.01)
        total += _
    print(total)
mysum1()

在這裡執行被裝飾函數執行前的增強操作
5050
在這裡執行被裝飾函數執行前的增強操作

由上面可以看出,如果要裝飾某個函數,只需要在定義這個函數時,在def語句的上一行添加@裝飾器函數即可。
對於裝飾器裝飾一個函數:

@decorator
def myfun():
    print("hello")

上面的代碼等價於:

def myfun():
    print("hello")
   
myfun = decorator(myfun)

當一個裝飾器裝飾函數時,函數的功能增強了,因為在調用這個函數時,實際上調用的是在定義裝飾器函數時,其內部函數。而此時內部函數是由增強功能命令和原被裝飾函數組成。
創建一個統計函數運行時長的裝飾器

import time


def decorator1(func):
    def inner():
        begin = time.time()

        func()  # 執行被裝飾的函數

        end = time.time()
        print(f"函數`{func.__name__}`運行的總時間為:{end - begin:.3} 秒")

    return inner


@decorator1
def mysum1():
    from time import sleep
    total = 0
    for _ in range(101):
        sleep(0.01)
        total += _
    print(total)


mysum1()

5050
函數mysum1運行的總時間為:1.59 秒

5.4.4.2.2 被裝飾函數接收參數

在上面的例子中,通過裝飾器函數decorator裝飾的函數是不能有輸入參數的,在實際使用中並不是很方便。
通過對裝飾器進行改造可以避免這種情況,從而使裝飾器函數有更廣泛的用途。
裝飾器定義:讓被裝飾函數接收參數

import time


def decorator2(func):
    def inner(*args, **kwargs):
        begin = time.time()

        func(*args, **kwargs)  # 執行被裝飾的函數

        end = time.time()
        print(f"函數`{func.__name__}`運行的總時間為:{end - begin:.3}")

    return inner

需要改造的地方:
1、為裝飾器函數decorator的內部函數inner在定義時增加收集位置形參和收集關鍵字形參
2、在裝飾器函數decorator的內部函數inner函數體中,執行被裝飾器裝飾的函數func時,通過參數解包的方式傳入參數。
裝飾帶有參數的函數:

@decorator2
def mysum2(a, b):
    from time import sleep
    total = a
    for _ in range(total + 1, b + 1):
        sleep(0.01)
        total += _
    print(total)


mysum2(1, 100)

5050
函數mysum1運行的總時間為:1.56

5.4.4.2.3 裝飾器函數接收參數

通過上面對裝飾器進行改造,可以使的被裝飾的函數可以輸入參數。上面的裝飾器函數decorator2可以計算被裝飾的函數執行時間,但是只能獲取到執行一次的時間。如果想要通過參數獲取執行任一次的時間,則需要使得裝飾器可以接收參數。
裝飾器定義:裝飾器接收參數

import time


def decorator3(n):
    def inner(func):
        def wrapper(*args, **kwargs):
            begin = time.time()
            for _ in range(iteration):
                func(*args, **kwargs)
            end = time.time()
            print(f"函數`{func.__name__}`運行的總時間為:{end - begin:.3}")

        return wrapper

    return inner

需要改造的地方:
1、裝飾器函數此時並不是通過參數來傳入被裝飾的函數,而是定義裝飾器自己的參數,演示時使用的是一個位置參數n,在後續如果遇到比較複雜的情況下也可以使用關鍵字形參、*args、**kwargs等收集參數。
2、內部函數inner用來收集被裝飾的函數。
3、內部函數inner的內部函數wrapper用來收集被裝飾的函數的參數。並編寫需要增強的命令。最終要執行被裝飾的函數其實就是執行這個wrapper函數。
裝飾器接收參數:

import time


def decorator3(n):
    def inner(func):
        def wrapper(*args, **kwargs):
            begin = time.time()
            for _ in range(n):
                func(*args, **kwargs)
            end = time.time()
            print(f"函數`{func.__name__}`運行的總時間為:{end - begin:.3}")

        return wrapper

    return inner


@decorator3(10)
def mysum3(a, b):
    from time import sleep
    total = a
    for _ in range(total + 1, b + 1):
        sleep(0.01)
        total += _
    print(total)


mysum3(1, 10)
# 等價於:mysum3 = decorator3(10)(mysum3)

55
55
55
55
55
55
55
55
55
55
函數mysum3運行的總時間為:1.41

5.4.4.2.4 裝飾器的返回值

如果你瞭解了上一節的內容,很容易想到只要在wrapper函數中return就是被裝飾函數的返回值。
裝飾器定義:接收被裝飾函數的返回值

import time


def decorator3(n):
    def inner(func):
        def wrapper(*args, **kwargs):
            begin = time.time()
            for _ in range(n):
                func(*args, **kwargs)
            end = time.time()
            print(f"函數`{func.__name__}`運行的總時間為:{end - begin:.3}")
            return end - begin
        return wrapper

    return inner

1、在上面的代碼中,return end – begin就是被裝飾器的返回值。可以通過變數進行接收。

@decorator3(3)
def mysum3(a, b):
    from time import sleep
    total = a
    for _ in range(total + 1, b + 1):
        sleep(0.01)
        total += _
    print(total)


total_time = mysum3(1, 10)
print(total_time)

函數mysum3運行的總時間為:1.42
1.4218323230743408

5.4.4.2.5 多個裝飾器裝飾同一個函數

對於某個函數,可以使用多個裝飾器對其進行裝飾,寫法如下:

@decorator1
@decorator2
def 被裝飾函數():
	pass

對於被多個裝飾器裝飾的函數,其裝飾順序為由最近到遠,即decorator2會先裝飾,然後是decorator1。

從自動化辦公到智能化辦公
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 動態鏈接庫(dynamic link library)介紹 代碼放到exe中,肯定會造成磁碟冗餘; 電腦ABCD四個軟體,lib加入到代碼中不是在編譯期進入的,而是在運行期 (A進程啟動,把dll加入到A進程中……),編譯的時候不需要這份代碼, 尾碼是.dll 如果要更新軟體,把dll換掉就可以了, ...
  • 卸載mysql教程: (261條消息) 如何徹底卸載 MySQL ,再可重新安裝 MySQL_Cavalier_01的博客-CSDN博客_如何卸載mysql重新安裝 安裝mysql5.7.19: 首先,去官網下載5.7.19壓縮包然後解壓; MySQL :: Download MySQL Commu ...
  • 一:背景 1. 講故事 上一篇寫完了之後,馬上就有朋友留言對記錄行的 8060byte 限制的疑惑,因為他的表記錄存儲了大量的文章,存儲文章的欄位類型用的是 nvarchar(max),長度很顯然是超過 8060byte 的,請問這個底層是怎麼破掉 8060byte 的限制的? 說實話這是一個好問題 ...
  • 為了應對大流量,現代應用/中間件通常採用分散式部署,此時不得不考慮 CAP 問題。ZooKeeper(後文簡稱 ZK)是面向 CP 設計的一個開源的分散式協調框架,將那些複雜且容易出錯的分散式一致性服務封裝起來,構成一個高效可靠的原語集,並以一系列簡單易用的介面提供給用戶使用,分散式應用程式可以基於... ...
  • 力扣104 求二叉樹的最大深度 題目: 給定一個二叉樹,找出其最大深度。 二叉樹的深度為根節點到最遠葉子節點的最長路徑上的節點數。 說明: 葉子節點是指沒有子節點的節點。 示例 給定二叉樹 [3,9,20,null,null,15,7], 3 / \ 9 20 / \ 15 7 返回它的最大深度 3 ...
  • 力扣100 相同的樹 題目: 給你兩棵二叉樹的根節點 p 和 q ,編寫一個函數來檢驗這兩棵樹是否相同。 如果兩個樹在結構上相同,並且節點具有相同的值,則認為它們是相同的。 示例 1: 輸入:p = [1,2,3], q = [1,2,3] 輸出:true 示例 2: 輸入:p = [1,2], q ...
  • RequestMappingHandlerAdapter是Spring Web MVC中針對@Controller和@RequestMapping體系的處理器適配器,本文對RequestMappingHandlerAdapter的組成、初始化以及同步請求處理流程進行詳細梳理和總結。 ...
  • 家居網購項目實現012 以下皆為部分代碼,詳見 https://github.com/liyuelian/furniture_mall.git 29.功能27-Ajax檢驗註冊名 29.1需求分析/圖解 用戶註冊時,後端通過驗證,提示用戶當前輸入的用戶名是否可用。 29.2思路分析 29.3代碼實現 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...