最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Python基礎(chǔ)入門之遞歸深度限制與尾遞歸優(yōu)化詳解

 更新時(shí)間:2026年07月26日 09:27:11   作者:星河耀銀海  
本文主要為你簡(jiǎn)單介紹Python遞歸優(yōu)化的實(shí)戰(zhàn)方法,從尾遞歸概念、修改遞歸深度限制,到手動(dòng)將遞歸改寫為迭代、使用顯式棧模擬,以及記憶化緩存提升性能,幫你徹底解決遞歸深度和性能問題

一、開篇:遞歸的性能瓶頸

遞歸代碼優(yōu)雅、簡(jiǎn)潔,但它有一個(gè)致命弱點(diǎn):每次遞歸調(diào)用都會(huì)在調(diào)用棧上新增一幀(frame),消耗內(nèi)存。如果遞歸層數(shù)太多,就會(huì)觸發(fā)Python的"遞歸深度限制"——RecursionError。

先看問題:

# 普通遞歸求和
def sum_recursive(n):
    if n <= 0:
        return 0
    return n + sum_recursive(n - 1)

# sum_recursive(1000)  # RecursionError!
# Python默認(rèn)遞歸深度限制為1000

import sys
print(f"默認(rèn)限制: {sys.getrecursionlimit()}")  # 1000

# 為什么有限制?
# 每次遞歸調(diào)用,Python都需要:
# 1. 在調(diào)用棧上創(chuàng)建新的棧幀
# 2. 保存局部變量和返回地址
# 3. 消耗內(nèi)存(通常每個(gè)棧幀~1KB)
# 1000層遞歸 ≈ 1MB 棧內(nèi)存
# 更深的話可能導(dǎo)致棧溢出(Stack Overflow)

這篇文章,我們來探討如何處理遞歸深度問題:尾遞歸優(yōu)化(以及為什么Python不支持它)、手動(dòng)改寫遞歸為迭代、記憶化緩存優(yōu)化,以及一些實(shí)用的替代方案。

二、尾遞歸:概念與Python的現(xiàn)實(shí)

2.1 什么是尾遞歸

# 普通遞歸——遞歸調(diào)用后還有操作(乘法)
def factorial_normal(n):
    if n <= 1:
        return 1
    return n * factorial_normal(n - 1)
    #     ↑ 遞歸調(diào)用后還要做乘法——不是尾遞歸

# 尾遞歸——遞歸調(diào)用是函數(shù)的最后一步
def factorial_tail(n, accumulator=1):
    if n <= 1:
        return accumulator
    return factorial_tail(n - 1, n * accumulator)
    #     ↑ 遞歸調(diào)用是最后一步,結(jié)果直接返回——這是尾遞歸

# ?? 尾遞歸的優(yōu)勢(shì):
# 編譯器和解釋器可以優(yōu)化尾遞歸——不創(chuàng)建新的棧幀
# 而是復(fù)用當(dāng)前的棧幀(因?yàn)楫?dāng)前幀已經(jīng)沒用了)
# 這意味著:尾遞歸理論上可以無限深,不會(huì)棧溢出!

# ?? 可視化對(duì)比:
def normal_recursion(n):
    """普通遞歸——調(diào)用后還有操作"""
    if n <= 0:
        return 0
    result = normal_recursion(n - 1)  # 保存result
    return n + result                  # ← 還要用n

def tail_recursion(n, acc=0):
    """尾遞歸——調(diào)用后沒有額外操作"""
    if n <= 0:
        return acc
    return tail_recursion(n - 1, acc + n)  # ← 直接返回,不需要保留n

2.2 Python不支持尾遞歸優(yōu)化

# ?? 重要:Python官方不支持尾遞歸優(yōu)化(TCO, Tail Call Optimization)

# 即使寫成尾遞歸的形式,Python仍然會(huì)創(chuàng)建新的棧幀!
# 所以 factorial_tail(1000) 仍然會(huì)觸發(fā) RecursionError

# 為什么Python不支持?
# 1. Guido van Rossum(Python之父)認(rèn)為TCO會(huì)破壞調(diào)試信息
#    ——尾遞歸優(yōu)化會(huì)丟失調(diào)用棧的中間幀
# 2. Python的哲學(xué):"應(yīng)該只有一種明顯的方式來做一件事"
#    而迭代(循環(huán))就是Python推薦的方式
# 3. Python的動(dòng)態(tài)特性使得TCO的實(shí)現(xiàn)復(fù)雜化

# ?? 所以結(jié)論是:
# Python中用遞歸時(shí),要時(shí)刻注意深度限制
# 大數(shù)據(jù)量用迭代,小數(shù)據(jù)量用遞歸
# 不要指望尾遞歸優(yōu)化來救你

三、遞歸深度限制管理

3.1 查看和修改遞歸深度限制

import sys

# 查看當(dāng)前限制
current_limit = sys.getrecursionlimit()
print(f"當(dāng)前遞歸深度限制: {current_limit}")  # 通常是1000

# 修改限制
sys.setrecursionlimit(5000)
print(f"修改后: {sys.getrecursionlimit()}")  # 5000

# ?? 警告:
# 1. 提高限制有風(fēng)險(xiǎn)——可能導(dǎo)致棧溢出導(dǎo)致Python崩潰
# 2. 操作系統(tǒng)對(duì)棧大小有限制(Windows默認(rèn)1MB,Linux默認(rèn)8MB)
# 3. 提高限制是治標(biāo)不治本——代碼邏輯才是關(guān)鍵
# 4. 恢復(fù)默認(rèn)值
sys.setrecursionlimit(1000)

# 檢查某個(gè)函數(shù)需要多深的遞歸
def measure_recursion_depth(n, current=0):
    """測(cè)量遞歸深度"""
    if n <= 0:
        return current
    return measure_recursion_depth(n - 1, current + 1)

# 安全測(cè)試
test_depths = [10, 100, 500]
for d in test_depths:
    depth = measure_recursion_depth(d)
    print(f"n=wppm3vysvbp, 實(shí)際遞歸深度={depth}")

3.2 安全處理RecursionError

def safe_recursive_computation(n, max_depth=900):
    """帶深度保護(hù)的遞歸計(jì)算"""
    def inner(n, depth):
        if depth > max_depth:
            raise RecursionError(f"超過安全深度限制 {max_depth}")

        if n <= 1:
            return n

        return inner(n - 1, depth + 1) + inner(n - 2, depth + 1)

    try:
        return inner(n, 0)
    except RecursionError as e:
        print(f"?? 遞歸深度超限: {e}")
        print(f"   請(qǐng)減小輸入規(guī)?;蚴褂玫姹?)
        return None

print(safe_recursive_computation(10))    # 正常
print(safe_recursive_computation(1000))  # 超限

四、將遞歸改寫為迭代

4.1 簡(jiǎn)單的尾遞歸轉(zhuǎn)循環(huán)

# ?? 尾遞歸可以很自然地轉(zhuǎn)成while循環(huán)

# 尾遞歸版本
def sum_tail(n, acc=0):
    if n <= 0:
        return acc
    return sum_tail(n - 1, acc + n)

# 轉(zhuǎn)成迭代——幾乎是一一對(duì)應(yīng)的翻譯
def sum_iterative(n):
    """尾遞歸 → while循環(huán)"""
    acc = 0  # 對(duì)應(yīng)尾遞歸的accumulator
    while n > 0:  # 對(duì)應(yīng)遞歸條件
        acc = acc + n  # 更新accumulator
        n = n - 1  # 更新參數(shù)
    return acc  # 基準(zhǔn)條件的結(jié)果

print(sum_iterative(100))  # 5050

# 階乘的迭代版
def factorial_iterative(n):
    """階乘——遞歸轉(zhuǎn)迭代"""
    result = 1
    for i in range(1, n + 1):
        result *= i
    return result

print(factorial_iterative(10))  # 3628800

4.2 使用顯式棧模擬遞歸

# 對(duì)于樹遍歷這類"自然遞歸"的問題,可以用顯式棧模擬

# 遞歸版本——二叉樹前序遍歷
class TreeNode:
    def __init__(self, value, left=None, right=None):
        self.value = value
        self.left = left
        self.right = right

def preorder_recursive(root):
    """遞歸版前序遍歷"""
    if root is None:
        return []
    return (
        [root.value] +
        preorder_recursive(root.left) +
        preorder_recursive(root.right)
    )

# 迭代版本——使用顯式棧
def preorder_iterative(root):
    """迭代版前序遍歷——用棧模擬遞歸"""
    if root is None:
        return []

    result = []
    stack = [root]  # 顯式維護(hù)調(diào)用棧

    while stack:
        node = stack.pop()  # "彈棧"
        result.append(node.value)

        # 先壓右,再壓左(因?yàn)闂J荓IFO)
        if node.right:
            stack.append(node.right)
        if node.left:
            stack.append(node.left)

    return result

# 測(cè)試
tree = TreeNode(1,
    TreeNode(2, TreeNode(4), TreeNode(5)),
    TreeNode(3, None, TreeNode(6))
)
print(preorder_recursive(tree))   # [1, 2, 4, 5, 3, 6]
print(preorder_iterative(tree))   # [1, 2, 4, 5, 3, 6]

五、記憶化遞歸:用緩存拯救性能

5.1 手動(dòng)實(shí)現(xiàn)記憶化

# 斐波那契數(shù)列的三種實(shí)現(xiàn)——性能天差地別

# 版本一:樸素遞歸——O(2^n),極慢
def fib_naive(n):
    if n <= 1:
        return n
    return fib_naive(n - 1) + fib_naive(n - 2)

# 版本二:記憶化遞歸——O(n),很快
def fib_memoized(n, memo=None):
    """記憶化——用字典緩存已計(jì)算的結(jié)果"""
    if memo is None:
        memo = {}

    if n in memo:
        return memo[n]  # 直接返回緩存

    if n <= 1:
        return n

    memo[n] = fib_memoized(n - 1, memo) + fib_memoized(n - 2, memo)
    return memo[n]

# 版本三:迭代——O(n),最快
def fib_iterative(n):
    if n <= 1:
        return n
    a, b = 0, 1
    for _ in range(2, n + 1):
        a, b = b, a + b
    return b

# 性能對(duì)比
import time
n = 35

start = time.perf_counter()
print(f"樸素遞歸 fib({n}) = {fib_naive(n)}")
print(f"耗時(shí): {time.perf_counter() - start:.4f}秒")
# 約1-3秒

start = time.perf_counter()
print(f"記憶化遞歸 fib({n}) = {fib_memoized(n)}")
print(f"耗時(shí): {time.perf_counter() - start:.6f}秒")
# 約0.0001秒

start = time.perf_counter()
print(f"迭代版 fib({n}) = {fib_iterative(n)}")
print(f"耗時(shí): {time.perf_counter() - start:.6f}秒")
# 約0.00001秒

5.2 使用functools.lru_cache

from functools import lru_cache

# @lru_cache是Python官方提供的記憶化裝飾器
# 它自動(dòng)緩存函數(shù)的返回值

@lru_cache(maxsize=None)  # maxsize=None → 無限緩存
def fib_cached(n):
    """使用lru_cache的斐波那契——代碼簡(jiǎn)潔又高效"""
    if n <= 1:
        return n
    return fib_cached(n - 1) + fib_cached(n - 2)

# 計(jì)算fib(100)也不會(huì)卡!
print(fib_cached(100))  # 354224848179261915075

# 查看緩存信息
print(f"緩存信息: {fib_cached.cache_info()}")
# CacheInfo(hits=98, misses=101, maxsize=None, currsize=101)

# 清除緩存
fib_cached.cache_clear()

# lru_cache參數(shù)說明
# maxsize: 最大緩存條目數(shù)(默認(rèn)128),None表示無限制
# typed: 是否區(qū)分參數(shù)類型(例如1和1.0是否區(qū)分)

@lru_cache(maxsize=256)
def expensive_computation(x, y):
    """模擬耗時(shí)計(jì)算"""
    import time
    time.sleep(1)  # 模擬耗時(shí)
    return x * y + x + y

# 第一次調(diào)用——慢
result1 = expensive_computation(10, 20)  # 1秒

# 第二次調(diào)用相同參數(shù)——瞬間返回(命中緩存)
result2 = expensive_computation(10, 20)  # 瞬間

print(f"相同結(jié)果: {result1 == result2}")  # True

六、Trampoline模式:模擬尾遞歸

# Trampoline(蹦床)模式
# 雖然Python不支持TCO,但可以手動(dòng)模擬

# 思路:不直接在遞歸中調(diào)用,而是返回一個(gè)"描述下一步調(diào)用"的對(duì)象
# 在外部循環(huán)中執(zhí)行這些調(diào)用

def trampoline(f):
    """蹦床執(zhí)行器——處理返回的函數(shù)調(diào)用"""
    def wrapper(*args, **kwargs):
        result = f(*args, **kwargs)
        # 只要結(jié)果是可調(diào)用的,就繼續(xù)執(zhí)行
        while callable(result):
            result = result()
        return result
    return wrapper

@trampoline
def factorial_trampoline(n, acc=1):
    """使用trampoline的尾遞歸階乘"""
    if n <= 1:
        return acc
    # 不直接調(diào)用,而是返回一個(gè)lambda
    return lambda: factorial_trampoline(n - 1, n * acc)

# 現(xiàn)在可以計(jì)算大數(shù)的階乘了
print(factorial_trampoline(5))    # 120
print(factorial_trampoline(100))  # 很大的數(shù)...

# ?? trampoline模式在實(shí)踐中很少使用——太繞了
# 大多數(shù)情況下,直接把遞歸轉(zhuǎn)成迭代更簡(jiǎn)單

七、總結(jié)

雖然遞歸優(yōu)雅,但Python對(duì)遞歸的支持有限。理解遞歸的局限性和替代方案,是成為成熟的Python開發(fā)者的必經(jīng)之路。

核心要點(diǎn):

  1. Python不支持尾遞歸優(yōu)化——別指望它能解決深度問題
  2. 默認(rèn)遞歸深度限制1000層——sys.setrecursionlimit()可以修改但要謹(jǐn)慎
  3. 記憶化(lru_cache)——最適合優(yōu)化有重復(fù)計(jì)算的遞歸
  4. 迭代改寫——最可靠的解決方案,把遞歸轉(zhuǎn)成循環(huán)
  5. 顯式棧——對(duì)于樹/圖等結(jié)構(gòu),用列表模擬調(diào)用棧

遞歸最佳實(shí)踐:

  • 遞歸深度 < 1000:放心用
  • 有重復(fù)計(jì)算:加@lru_cache
  • 深度不可控:改用迭代
  • 樹/圖遍歷:用顯式棧+循環(huán)(或lru_cache遞歸)

到此這篇關(guān)于Python基礎(chǔ)入門之遞歸的深度限制與性能優(yōu)化詳解的文章就介紹到這了,更多相關(guān)Python遞歸深度問題與優(yōu)化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • pytest多文件執(zhí)行順序控制詳解

    pytest多文件執(zhí)行順序控制詳解

    默認(rèn)情況下pytest測(cè)試用例的執(zhí)行順序是先按照外層后內(nèi)層(目錄下的文件),同層級(jí)的包或文件、根據(jù)名稱、按照ascii碼升序執(zhí)行,文件內(nèi)的用例根據(jù)先后順序執(zhí)行,這篇文章主要給大家介紹了關(guān)于pytest多文件執(zhí)行順序控制的相關(guān)資料,需要的朋友可以參考下
    2022-07-07
  • Pandas?DataFrame數(shù)據(jù)修改值的方法

    Pandas?DataFrame數(shù)據(jù)修改值的方法

    本文主要介紹了Pandas?DataFrame修改值,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • Pandas DataFrame求差集的示例代碼

    Pandas DataFrame求差集的示例代碼

    這篇文章主要介紹了Pandas DataFrame求差集的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-12-12
  • 對(duì)numpy數(shù)據(jù)寫入文件的方法講解

    對(duì)numpy數(shù)據(jù)寫入文件的方法講解

    今天小編就為大家分享一篇對(duì)numpy數(shù)據(jù)寫入文件的方法講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-07-07
  • python基礎(chǔ)之匿名函數(shù)介紹

    python基礎(chǔ)之匿名函數(shù)介紹

    大家好,本篇文章主要講的是python基礎(chǔ)之匿名函數(shù)介紹,感興趣的同學(xué)趕快來看一看,對(duì)你有幫助的話記得收藏一下,方便下次瀏覽
    2021-12-12
  • 基于python分析你的上網(wǎng)行為 看看你平時(shí)上網(wǎng)都在干嘛

    基于python分析你的上網(wǎng)行為 看看你平時(shí)上網(wǎng)都在干嘛

    這篇文章主要介紹了基于python分析你的上網(wǎng)行為 看看你平時(shí)上網(wǎng)都在干嘛,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-08-08
  • 用Python做個(gè)自動(dòng)化彈鋼琴腳本實(shí)現(xiàn)天空之城彈奏

    用Python做個(gè)自動(dòng)化彈鋼琴腳本實(shí)現(xiàn)天空之城彈奏

    突然靈機(jī)一動(dòng),能不能用Python自動(dòng)化腳本彈奏一曲美妙的鋼琴曲呢?今天就一起帶大家如何用Python實(shí)現(xiàn)自動(dòng)化彈出一首《天空之城》有需要的朋友可以借鑒參考下
    2021-09-09
  • python壓縮和解壓縮模塊之zlib的用法

    python壓縮和解壓縮模塊之zlib的用法

    這篇文章主要介紹了python壓縮和解壓縮模塊之zlib的用法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • 詳解Django admin高級(jí)用法

    詳解Django admin高級(jí)用法

    這篇文章主要介紹了Django admin高級(jí)用法,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-11-11
  • Python實(shí)現(xiàn)網(wǎng)絡(luò)端口轉(zhuǎn)發(fā)和重定向的方法

    Python實(shí)現(xiàn)網(wǎng)絡(luò)端口轉(zhuǎn)發(fā)和重定向的方法

    這篇文章主要介紹了Python實(shí)現(xiàn)網(wǎng)絡(luò)端口轉(zhuǎn)發(fā)和重定向的方法,結(jié)合實(shí)例形式分析了Python基于threading和socket模塊實(shí)現(xiàn)端口轉(zhuǎn)發(fā)與重定向的具體操作技巧,需要的朋友可以參考下
    2016-09-09

最新評(píng)論

十堰市| 和静县| 西吉县| 榆林市| 乐清市| 扎囊县| 毕节市| 庄河市| 霍林郭勒市| 平乡县| 武宁县| 井研县| 洛隆县| 饶阳县| 响水县| 宜兰县| 通州区| 从江县| 腾冲县| 泸西县| 罗甸县| 泌阳县| 武清区| 灵川县| 徐闻县| 黄冈市| 福州市| 屯门区| 子长县| 三亚市| 望城县| 铜陵市| 东丽区| 利川市| 双城市| 宁远县| 砀山县| 全州县| 靖远县| 德庆县| 错那县|