pyinstaller打包报错: RecursionError: maximum recursion depth exceeded

今天给以前写的python脚本用pyinstaller打包的时候,居然报错了:

... 
 File "d:\programdata\anaconda3\lib\ast.py", line 253, in visit
    return visitor(node)
  File "d:\programdata\anaconda3\lib\ast.py", line 261, in generic_visit
    self.visit(item)
  File "d:\programdata\anaconda3\lib\ast.py", line 253, in visit
    return visitor(node)
  File "d:\programdata\anaconda3\lib\ast.py", line 263, in generic_visit
    self.visit(value)
  File "d:\programdata\anaconda3\lib\ast.py", line 253, in visit
    return visitor(node)
RecursionError: maximum recursion depth exceeded

看上去似乎是某个库自己递归遍历玩的太嗨了,超过了python的栈高度限制

搜索了一番,很快找到了解决办法: https://stackoverflow.com/questions/38977929/pyinstaller-creating-exe-runtimeerror-maximum-recursion-depth-exceeded-while-ca

 

在此总结下解

<think>我们正在讨论一个特殊场景:用户遇到了RecursionError,但实际递归深度并未达到1000次(默认限制)。这看起来违反直觉,但确实可能发生。以下我将分析可能原因并提供解决方案。 ### 原因分析 1. **多线程/异步环境中的递归限制** 在Python中,递归深度限制是**针对每个线程独立计算**的。如果程序使用多线程,每个线程的递归深度单独计数。但若主线程或其他线程同时进行深层递归,可能触发全局限制[^1][^2]。 2. **递归函数内调用其他递归函数** 递归函数A调用递归函数B时,两者的调用栈会叠加。例如: ```python def func_A(n): if n == 0: return func_B(n) # 调用另一个递归函数 def func_B(n): if n == 0: return func_A(n-1) # 互相调用 ``` 此时总递归深度为`func_A`和`func_B`的调用次数之和,即使单函数未超限,总和也可能超限[^3]。 3. **C扩展或内置函数递归** 部分C扩展模块(如NumPy、Pandas)或Python内置函数(如`__str__`、`__repr__`)可能隐式递归。例如: ```python class Node: def __init__(self, children): self.children = children def __repr__(self): return f"Node({self.children})" # 递归调用子节点的__repr__ # 循环引用导致无限递归 node = Node([]) node.children.append(node) print(node) # 触发RecursionError ``` 此类递归不直接可见,但会消耗栈深度[^4]。 4. **装饰器或元类导致的间接递归** 装饰器可能意外增加递归深度。例如: ```python def debug_decorator(func): def wrapper(*args): print("Calling", func.__name__) return func(*args) # 调用原函数 return wrapper @debug_decorator def recursive_func(n): if n == 0: return recursive_func(n-1) # 实际递归深度 = n + 装饰器调用次数 ``` 5. **系统栈空间不足** 操作系统对线程栈大小有限制(如Linux默认8MB)。即使递归次数较少,若单次调用占用栈过大(如大型局部变量),也可能提前触发栈溢出[^5]。 ### 解决方案 #### 1. 检查代码中的间接递归 - 排查是否存在函数间相互调用(如A→B→A)。 - 使用`sys._getframe().f_back`跟踪调用栈: ```python import sys def print_stack(depth=0): frame = sys._getframe() stack = [] while frame: stack.append(f"{frame.f_code.co_name}:{frame.f_lineno}") frame = frame.f_back print(f"Current depth: {len(stack)}") print("Stack trace:\n" + "\n".join(stack[-10:])) # 打印最后10层 ``` #### 2. 避免对象自引用 - 对包含循环引用的对象,重写`__repr__`或`__str__`方法: ```python class Node: def __init__(self, children): self.children = children def __repr__(self): return "Node(children)" # 避免递归输出子节点 ``` #### 3. 增加线程栈大小 - 对于多线程场景,可通过`threading.stack_size()`调整: ```python import threading threading.stack_size(1024*1024*32) # 32MB ``` 注意:必须在创建线程前设置。 #### 4. 使用迭代重写递归 将递归改为迭代(参考前文方法),这是最彻底的解决方案。 #### 5. 监控实际递归深度 在递归函数中添加计数器: ```python import sys def recursive_func(n, depth=0): if depth > 500: # 设置安全阈值 raise RuntimeError("Depth exceeds safe limit") # ...正常递归逻辑... recursive_func(n-1, depth+1) ``` ### 特殊情况:递归深度未达1000却报错 若递归深度明显小于1000仍报错,可能是: - **局部变量过大**:单个函数调用占用大量栈空间(如`local_var = [0]*1000000`)。 **解决**:减少栈占用,或用堆分配(如`global`或`heapq`)。 - **第三方库bug**:如旧版pyinstaller或tokenizer的已知问题[^4][^5]。 **解决**:更新库版本或应用补丁。 ### 总结流程 ```mermaid graph TD A[出现RecursionError但递归深度<1000] --> B{检查原因} B --> C1[多线程/异步环境] B --> C2[函数间递归调用] B --> C3[隐式递归(如__repr__)] B --> C4[局部变量过大] B --> C5[第三方库问题] C1 --> D1[增加线程栈大小] C2 --> D2[改为迭代或消除交叉递归] C3 --> D3[重写特殊方法] C4 --> D4[减少栈占用] C5 --> D5[更新库版本] ``` 通过以上方法,您可定位并解决非常规递归深度问题。若仍无法解决,建议提供最小可复现代码以便进一步分析。 §§相关问题§§ 1. 如何检测Python程序中的循环引用? 2. 在多线程环境中如何安全地使用递归? 3. 如何计算Python函数的栈空间占用? 4. 哪些Python内置函数可能导致隐式递归? 5. 如何为包含递归的Python程序设置线程栈大小? [^1]: 引用自:RecursionError: maximum recursion depth exceeded原因及解决办法。 [^2]: 引用自:【Python】解决Python报错RecursionError: maximum recursion depth exceeded。 [^3]: 引用自:Python报错RecursionError: maximum recursion depth exceeded。 [^4]: 引用自:tokenizer出现RecursionError: maximum recursion depth exceeded while getting the str of an object的解决方案。 [^5]: 引用自:Python:pyinstaller报错【A RecursionError maximum recursion depth exceeded occurred】。</think>### Python RecursionError 但递归深度未达1000的原因与解决方案 当出现 `RecursionError: maximum recursion depth exceeded` 但实际递归调用次数未达到默认1000次限制时,通常有以下特殊原因及解决方案: --- #### 一、常见原因分析 1. **隐式递归调用** - 对象方法(如 `__str__`, `__repr__`, `__getattr__`)被意外递归调用 - **示例**: ```python class Node: def __init__(self, value): self.value = value self.children = [] def __repr__(self): # 错误:递归调用子节点的__repr__ return f"Node({self.value}, {self.children})" root = Node(1) child = Node(2) root.children.append(child) child.children.append(root) # 循环引用 print(root) # 触发RecursionError ``` *引用自对象循环引用场景[^4]* 2. **装饰器叠加递归** - 多层装饰器导致实际调用深度倍增 - **示例**: ```python def debug_decorator(func): def wrapper(*args): print(f"Calling {func.__name__}") return func(*args) # 装饰器本身增加调用深度 return wrapper @debug_decorator def recursive_func(n): if n <= 0: return recursive_func(n-1) # 实际深度 = n * 装饰器层数 ``` 3. **第三方库内部递归** - 使用库(如NLTK、SpaCy)时,其内部实现存在递归调用 - 典型场景:复杂文本解析、语法树生成[^4] 4. **系统栈空间不足** - 单次递归调用占用过大栈空间(如大型局部变量) - 递归深度远小于1000时即触发系统级栈溢出 5. **元类/metaclass 递归** - 元类中的 `__new__` 或 `__prepare__` 方法意外递归 --- #### 二、针对性解决方案 ##### 1. 对象循环引用问题 ```python class Node: def __init__(self, value): self.value = value self.children = [] def __repr__(self): # 安全方案:限制递归深度 return f"Node({self.value}, children_count={len(self.children)})" ``` ##### 2. 装饰器深度优化 ```python import functools def debug_decorator(func): @functools.wraps(func) def wrapper(*args): # 添加深度监控 if wrapper.depth > 100: raise RuntimeError("Depth limit reached") wrapper.depth += 1 result = func(*args) wrapper.depth -= 1 return result wrapper.depth = 0 return wrapper ``` ##### 3. 第三方库问题处理 - **步骤**: 1. 更新库到最新版本 2. 限制输入规模(如缩短文本长度) 3. 替换为迭代型工具(如SpaCy的`nlp.pipe`替代逐句处理) ##### 4. 栈空间优化 ```python def recursive_func(n, large_data=None): # 将大型数据移出栈帧 large_data = large_data or [] # 使用生成器减少内存占用 if n <= 0: yield else: yield from recursive_func(n-1, large_data) ``` ##### 5. 元类递归预防 ```python class SafeMeta(type): def __new__(cls, name, bases, dct): # 避免在__new__中创建同类实例 if not hasattr(cls, '_creating'): cls._creating = True instance = super().__new__(cls, name, bases, dct) delattr(cls, '_creating') return instance else: raise RuntimeError("Recursive metaclass call") ``` --- #### 三、调试工具与方法 1. **调用栈分析** ```python import sys import traceback def recursive_func(n): if n % 50 == 0: # 每50层打印栈信息 traceback.print_stack(limit=5) # ...递归逻辑... ``` 2. **深度监控装饰器** ```python def depth_monitor(func): def wrapper(*args, **kwargs): wrapper.count += 1 if wrapper.count > 500: raise RuntimeError(f"Depth exceeded at {wrapper.count}") result = func(*args, **kwargs) wrapper.count -= 1 return result wrapper.count = 0 return wrapper ``` 3. **内存分析工具** ```bash pip install memory-profiler mprof run --include-children your_script.py ``` --- #### 四、根本性预防策略 | **方案** | **适用场景** | **优势** | |-----------------------|----------------------------|-----------------------------| | 迭代替代递归 | 算法可改写 | 彻底避免递归限制 | | 尾递归优化 | 函数式编程场景 | 减少栈空间占用 | | 显式栈管理 | 复杂递归逻辑 | 完全控制调用深度 | | 分治+循环 | 大数据处理 | 并行处理能力 | > **关键提示**:当递归深度异常时,优先检查是否有**间接递归**(如通过属性访问触发的方法调用)而非直接递归调用[^4]。
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值