O(1)获取的变量:嵌套函数传参还是重复调用获取?
O(1)可获取的变量:嵌套函数传参还是重复计算?
先明确咱们纠结的两种写法:
写法A:嵌套函数传参
def bar(X, y, n_rows, n_cols): # Do stuff.... return stuff def foo(X, y, n_rows, n_cols): stuff = bar(X, y, n_rows, n_cols) # Do stuff... return stuff
写法B:嵌套函数内重复调用获取函数
def bar(X, y): n_rows = get_number_of_rows(X) # X.shape[0] n_cols = get_number_of_cols(X) # X.shape[1] # Do stuff.... return stuff def foo(X, y): n_rows = get_number_of_rows(X) # X.shape[0] n_cols = get_number_of_cols(X) # X.shape[1] stuff = bar(X, y) # Do stuff... return stuff
下面逐个回答你的问题:
1. 哪种写法可读性更好?
这得看参数数量和场景:
- 如果是像
n_rows、n_cols这种2-3个零散参数,写法A更清晰——调用bar的时候,一眼就能看到它依赖哪些变量,不用钻到bar的实现里去发现它偷偷调用了get_number_of_rows。维护的时候,别人看foo的代码,能立刻明白bar的输入是什么,不会被隐藏的依赖坑到。 - 如果参数多到4个以上,传参确实会显得繁琐,这时候可以考虑把相关参数打包成对象(比如Python的
dataclass、R的列表/S3对象),避免“参数爆炸”。 - 写法B的问题在于隐藏了依赖关系:阅读
foo的人不知道bar内部会去计算行数列数,万一后续X的结构变了,get_number_of_rows的逻辑需要调整,你得同时改foo和bar两处,而写法A只需要修改一次传参的源头。
2. 哪种写法性能更优?
核心取决于get_number_of_rows这类操作的时间复杂度:
- 如果是O(1)操作(比如Python里的
X.shape[0]、R里的nrow()),重复调用的性能差异几乎可以忽略不计——现代语言的解释器/编译器大多会做缓存优化,就算不优化,O(1)操作的耗时也微乎其微,完全不值得为这点性能牺牲可读性。 - 如果获取变量的操作是O(n)或者更慢(比如需要遍历整个数据集统计行数),那毫无疑问写法A更优,能避免重复计算带来的性能损耗。
但实际场景中,像行数、列数这种元数据,基本都是O(1)可获取的,所以性能不是这个问题的核心考量点,可读性优先。
3. 我们是否应通过编码避免出现这种场景?
当然可以通过一些编码技巧减少这种纠结:
- 打包相关参数:把和数据集相关的元数据(比如
n_rows、n_cols、甚至X本身)封装成一个统一的对象/结构体。比如在Python里可以写个简单的类:
然后函数只需要接受class DataContext: def __init__(self, X): self.X = X self.n_rows = X.shape[0] self.n_cols = X.shape[1]DataContext实例,不用传一堆零散参数,既清晰又避免重复计算。 - 利用上下文传递:如果是在类的方法中,可以把这些元数据作为实例变量,不用每次传参或者重复计算;如果是函数式编程场景,也可以用闭包或者上下文对象来传递环境变量。
- 不过如果只是2-3个参数,没必要过度设计——有时候直接传参反而比引入复杂结构更简单直白。
4. 不同语言对前两个问题的答案是否不同?
核心原则是一致的,但细节会有差异:
- 静态类型语言(Java、C++等):传参的写法更符合类型检查的要求,而且编译器的优化能力更强,重复调用O(1)函数的性能损耗几乎为0;如果参数多,用结构体/类打包是非常自然的选择。
- 动态语言(Python、R等):虽然也可以打包参数,但有时候直接传参更灵活;不过动态语言的解释器优化可能弱一点,但对于O(1)操作来说,这点差异完全可以忽略。
- 函数式语言(Haskell等):更倾向于纯函数设计,传参的写法更符合纯函数的要求(避免隐藏的依赖),但也可以通过monad或者上下文来传递共享变量,减少参数数量。
总结
优先考虑可读性:参数少选写法A,参数多就打包;性能只有当获取操作不是O(1)时才需要重点在意;可以通过封装优化来避免参数过多的尴尬场景;不同语言细节有差,但核心逻辑一致。
内容的提问来源于stack exchange,提问作者Matthaeus Gaius Caesar
相关产品推荐
相关产品推荐

