如何优雅解决Python多层函数调用中结果传递的冗余问题?
我编写了如下代码:
def a(): d_results = [] for i in range(10): b(i, d_results) # do something that needs d_results def b(i, d_results): # do clever b-stuff c(d_results) # more b-stuff def c(d_results): # do clever c-stuff d(d_results) # more c-stuff def d(d_results): result = ... d_results.append(result)
函数调用链为 a() -> b() -> c() -> d(),d() 生成的结果需要被 a() 收集。目前的做法是在各函数间传递 d_results 列表,但 b() 和 c() 完全不知道这个变量的用途,只是盲目传递,非常不便。
约束条件:
- 不能使用全局变量,因为这是HTTP请求处理器的一部分,数据需要保持请求本地特性
- 了解XSLT的隧道参数机制,但Python中没有类似功能
补充背景:d() 会将数据存入GAE数据存储,d_results 是这些操作的Futures对象,为了性能 d() 必须异步执行,所以需要在 a() 中处理这些Futures(因为迭代逻辑在 a() 里)。
1. 使用生成器传递结果
让 d() 返回结果,c() 传递 d() 的返回值,b() 传递 c() 的返回值,最终 a() 直接收集 b() 的返回值。中间函数无需关心结果用途,仅需传递返回值:
def a(): d_results = [] for i in range(10): future = b(i) d_results.append(future) # 处理d_results def b(i): # do clever b-stuff future = c() # more b-stuff return future def c(): # do clever c-stuff future = d() # more c-stuff return future def d(): result = ... # 生成Futures对象 return result
如果中间函数需要返回自身结果或存在多个结果,可以用生成器 yield 传递:
def a(): d_results = [] for i in range(10): for future in b(i): d_results.append(future) # 处理d_results def b(i): # do clever b-stuff yield from c() # more b-stuff def c(): # do clever c-stuff yield d() # more c-stuff def d(): result = ... return result
2. 使用上下文管理器实现请求本地存储
通过上下文管理器创建请求专属的存储容器,d() 可直接写入,a() 可直接读取,中间函数无需传递参数:
from contextlib import contextmanager _request_local = {} @contextmanager def request_context(): global _request_local _request_local = {'d_results': []} try: yield finally: _request_local.clear() def a(): with request_context(): for i in range(10): b(i) d_results = _request_local['d_results'] # 处理d_results def b(i): # do clever b-stuff c() # more b-stuff def c(): # do clever c-stuff d() # more c-stuff def d(): result = ... _request_local['d_results'].append(result)
这种方式保证每个请求的 d_results 独立,不会与其他请求混淆,满足请求本地的要求。
3. 使用依赖注入
定义一个结果收集器类,在 a() 中实例化后注入给后续函数,中间函数只需调用收集器接口,无需了解具体实现:
class ResultCollector: def __init__(self): self.results = [] def add_result(self, result): self.results.append(result) def a(): collector = ResultCollector() for i in range(10): b(i, collector) d_results = collector.results # 处理d_results def b(i, collector): # do clever b-stuff c(collector) # more b-stuff def c(collector): # do clever c-stuff d(collector) # more c-stuff def d(collector): result = ... collector.add_result(result)
相比直接传递列表,这种方式逻辑更清晰,中间函数明确知道自己在使用收集器,而非盲目传递变量,同时保持了请求本地特性(每个请求实例化新的收集器)。
当前的调用链本身不存在问题,但传递无意义变量说明中间函数与 d() 的耦合可以优化。上述方法中,生成器和依赖注入更符合Pythonic风格,上下文管理器则适合请求本地场景的全局访问需求。
内容的提问来源于stack exchange,提问作者vicmortelmans

