您已经在使用通用方法来处理函数调用顺序:将一个令牌从一个函数传递给另一个:
token1 = func1(token0)
token2 = func2(token1)
token3 = func2(token2)
...
通常,标记也是有用的结果。在你的例子中:如果你没有data 给它,你不会打电话给transform_data:data 是token0。因此,transform_data 将在 query_data 之后被调用。
但我认为你处理的真正问题是不同的:你担心用户给下一个函数提供了错误的输入,并且该函数可能会接受它并返回错误的结果:
我只是担心在一个混乱的过程中很难追踪你是否有一个有效的输入......稀疏矩阵是一种模型类型的无效输入,但对另一种模型类型有效 (cmets)
在静态类型的语言中,它(通常)不会发生,因为如果你在预期一只猫的时候给一只兔子,程序将无法编译。在 python 中,用鸭子打字,这并不容易。让我们看看如何执行此操作:
- 一个很好的文档
- 检查输入数据
- 对函数进行分组
- 锁定一些参数
良好的文档
这是迄今为止最好的解决方案。在这里,每个人都是成年人。不要太担心。
您可以使用 Python typing abilities 获取人类信息。
检查输入数据
这是Defensive programming 的变体。如果需要,包装你不拥有的函数:
def wrap_transform_data(data, arg1, arg2):
if not valid(data): # find a way to check this
raise Exception("data is not valid")
return transform_data(data, arg1, arg2)
使用异常并避免 try... except 阻塞,因为快速失败在这里更好。
有时这还不够。想象一下你有一个方阵:你如何检查它是否在下一个函数调用之前被转置?
对功能进行分组
只需将模块包装在另一个按顺序应用函数的函数中就足够简单了,但这似乎会产生一个不必要的复杂函数,其中包含许多违反单一职责的参数。
你会得到类似的东西:
def full_process(sql, arg1, arg2, frac_test, frac_validate, loss, learning_rate):
data=query_data(sql)
transformed_data=transform_data(data, arg1, arg2)
train, test, validate = train_test_validate(transformed_data, frac_test, frac_validate)
model = fit_model(train,test, loss, learning_rate)
predictions, f1 = model.predict(validate)
return ...
正如您所说,full_process 方法有很多混合参数。在 Python 中处理这个问题的常用方法是使用默认值:
def full_process(sql, arg1=1, arg2=2, frac_test=0.7, frac_validate=0.5, loss=0.2, learning_rate=0.1):
data=query_data(sql)
transformed_data=transform_data(data, arg1, arg2)
train, test, validate = train_test_validate(transformed_data, frac_test, frac_validate)
model = fit_model(train,test, loss, learning_rate)
predictions, f1 = model.predict(validate)
return ...
它变得可读,除非你每次都有不同的参数:
full_process(sql, frac_validate=0.9)
如果transpose 函数是其中之一,这将解决上面的转置矩阵示例。
注意:只对没有分支的部分进行分组。不要写这样的东西:
def full_process(sql, , case1, case2, case3, arg1=1, arg2=2, frac_test=0.7, frac_validate=0.5, loss=0.2, learning_rate=0.1):
data=query_data(sql)
if case1:
transformed_data=transform_data(data, arg1, arg2)
train, test, validate = train_test_validate(transformed_data, frac_test, frac_validate)
if case2:
...
else:
...
else:
transformed_data=transform_data2(data, arg1, arg2)
train, test, validate = train_test_validate(transformed_data, frac_test, frac_validate)
if case2:
...
else:
...
return ...
很难阅读和维护,这可能导致组合爆炸!
锁定部分参数
我将此添加为记录,但我认为这不是一个好的做法,除非在某些特定情况下。这类似于您对类的尝试,但使用了函数。
我关注这两行:
data=get_matrix()
transformed_data=transform_data(data, arg1, arg2)
如果要保证用户调用transform_data,返回值为query_data,可以返回一个函数:
def wrapped_query_data(sql):
def ret(arg1, arg2) # you might use functools.partial
transform_data(data, arg1, arg2)
return ret
现在的代码是:
data_transformer = wrapped_query_data(sql)
train_test_validate = data_transformer(arg1, arg2) # no way the user can twist data here
显然,如果您尝试概括这一点,您将需要一个包装器,并限制分支的可能性。
结论
是否有最好的方法来确保代码以特定的顺序运行并且该顺序在代码设计中是显而易见的?
请记住,Python 培养每个程序员都负责任。仅在需要时使用强制执行函数调用顺序的方法。