1. 程式人生 > 其它 >Python函式遞迴及三元表示式與匿名函式

Python函式遞迴及三元表示式與匿名函式

一、遞迴

1.1 函式遞迴呼叫介紹

函式不僅可以巢狀定義,還可以巢狀呼叫,即在呼叫一個函式的過程中,函式內部又呼叫另一個函式;而函式的遞迴呼叫指的是在呼叫一個函式的過程中又直接或間接地呼叫該函式本身。

例如:在呼叫f1的過程中,又呼叫f1,這就是直接呼叫函式f1本身:

def f1():
    print('from f1')
    f1()
f1()

在呼叫f1的過程中,又呼叫f2,而在呼叫f2的過程中又呼叫f1,這就是間接呼叫函式f1本身:

def f1():
    print('from f1')
    f2()
 
def f2():
    print('from f2')
    f1()
 
f1()

從上圖可以看出,兩種情況下的遞迴呼叫都是一個無限迴圈的過程,但Python中對函式的遞迴呼叫的深度做了限制(預設1000次),因而並不會像大家所想的那樣進入無限迴圈,會丟擲異常;

要避免出現這種情況,就必須讓遞迴呼叫在滿足某個特定條件下終止;

提示:

  1. 可以使用sys模組下的sys.getrecursionlimit()去檢視遞迴深度,預設值為1000,雖然可以使用sys.setrecursionlimit()去設定該值,但仍受限於主機作業系統棧大小的限制;
  2. Python不是一門函數語言程式設計語言,無法對遞迴進行尾遞迴優化;

1.2 回溯與遞推

下面會用一個淺顯的例子,為了讓讀者闡釋遞迴的原理和使用:

例1:

某公司四個員工坐在一起,問第四個人薪水,他說比第三個人多1000,問第三個人薪水,他說比第二個人多1000,問第二個人薪水,他說比第一個人多1000,最後第一個人說自己每月5000,請問第四個人的薪水是多少?

思路解析:

要知道第四個人的月薪,就必須知道第三個人的,第三個人的又取決於第二個人的,第二個人的又取決於第一個人的,而且每一個員工都比前一個多一千,數學表示式即:

salary(4) = salary(3) + 1000
salary(3) = salary(2) + 1000
salary(2) = salary(1) + 1000
salary(1) = 5000

總結為:

salary(n) = salary(n - 1) + 1000 (n > 1)
salary(1) = 5000  (n = 1)

很明顯這是一個遞迴的過程,可以將該過程分為兩個階段:回溯和遞推。

先進入回溯階段:要求第n個員工的薪水,需要回溯得到(n - 1)個員工的薪水,以此類推,直到得到第一個員工的薪水,此時,salary(1)已知,因而不必再向前回溯了;

然後進入遞推階段:從第一個員工的薪水可以推算出第二個員工的薪水(6000),從第二個員工的薪水可以推算出第三個員工的薪水(7000),以此類推,一直推算出第四個員工的薪水(8000)為止,遞迴結束;

需要注意的一點是,遞迴一定要有一個結束條件,這裡n = 1就是結束條件。

程式碼實現如下:

def salary(n):
    if n == 1:
        return 5000
    return salary(n - 1) + 1000

s = salary(4)
print(s)

執行結果:

8000

程式分析:

在未滿足n == 1的條件下,一直進行遞迴呼叫,即一直回溯;而在滿足n == 1的條件時,終止遞迴呼叫,即結束回溯,從而進入遞推階段,依次推導直到得到最終的結果。

遞迴本質就是在做重複的事情,所以理論上遞迴可以解決的問題迴圈也可以解決,只不過在某些情況下,使用遞迴會更容易實現,比如有一個巢狀多層的列表,要求打印出所有的元素,程式碼實現如下:

items = [[1, 2], 3, [4, [5, [6, 7]]]]
def foo(items):
    for i in items:
        if isinstance(i, list):  # 滿足未遍歷完items以及if判斷成立的條件時,一直進行遞迴呼叫
            foo(i) 
        else:
            print(i, end=' ')
 
foo(items) 

列印結果:

1 2 3 4 5 6 7 

使用遞迴,我們只需要分析出要重複執行的程式碼邏輯,然後提取進入下一次遞迴呼叫的條件或者說遞迴結束的條件即可,程式碼實現起來簡潔清晰。

二、三元表示式、列表生成式,字典生成式

2.1 三元表示式

三元表示式是Python為我們提供的一種簡化程式碼的解決方案,語法如下:

res = 條件成立時返回的值 if 條件 else 條件不成立時返回的值

針對下述場景:

def max2(x, y):
    if x > y:
        return x
    else:
        return y
 
res = max2(1, 2)

用三元表示式可以一行解決:

x = 1
y = 2
res = x if x > y else y  # 三元表示式

當功能需求僅僅是二選一的情況下,推薦使用三元表示式;儘量不要巢狀使用三元表示式。

2.2 列表生成式

列表生成式是Python為我們提供的一種簡化程式碼的解決方案,用來快速生成列表,語法如下:

[expression for item1 in iterable1 if condition1
for item2 in iterable2 if condition2
...
for itemN in iterableN if conditionN
]
 
"""類似於:"""
res = []
for item1 in iterable1:
    if condition1:
        for item2 in iterable2:
            if condition2
                ...
                for itemN in iterableN:
                    if conditionN:
                        res.append(expression)

針對下述場景:

egg_list = []
for i in range(10):
    egg_list.append('雞蛋%s' % i)

用列表生成式可以一行解決:

egg_list = ['雞蛋%s' % i for i in range(10)]

2.3 字典生成式

這裡先介紹一個Python內建的函式方法列舉:enumerate():用於將一個可遍歷的資料物件(如列表、元組或字串)組合為一個索引序列,同時列出資料和資料下標,也可以通過start引數控制下標起始位置,一般用在 for 迴圈當中。

字典生成式一般會搭配enumerate()一起使用,使用方法如下:

name_list = ['jason', 'kevin', 'tony', 'jerry']
res = {i: j for i, j in enumerate(name_list) if j != 'jason'}
print(res)

列印結果為:

{0: 'jason', 1: 'kevin', 2: 'tony', 3: 'jerry'}

三、匿名函式

3.1 什麼是匿名函式?

匿名函式就是沒有名字的函式,其語法格式如下:

lambda 形參:返回值

匿名函式與函式有相同的作用域,但是匿名意味著引用計數為0,使用一次就釋放,除非讓其有名字:

func = lambda x, y, z=1:x + y + z 
func(1, 2, 3)
"""但讓其有名字就失去了匿名函式的意義"""

3.2 有名函式與匿名函式的對比

  • 有名函式:迴圈使用,儲存了名字,通過名字就可以重複引用函式功能;
  • 匿名函式:一次性使用,隨時隨地定義;

匿名函式一般不會單獨使用,都是配合生成式或者其他函式一起使用,如:

max()
min()
sorted()
map()
reduce()
filter()
...