我对python设计模式还比较陌生,但是我想确保我的代码尽可能地易于理解和灵活。这是一个示例:
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)
每个功能必须按此顺序运行。在此特定示例中,顺序应该非常明显。但是在更复杂的模块中,可能会有分支路径,并且如果没有大量的文档应该以什么顺序运行这些函数,可能会不清楚。
只需将模块包装在另一个按顺序应用功能的功能中就足够简单了,但这似乎使不必要的复杂功能带有许多违反单一职责的参数。
我还尝试将每个函数包装在一个类中,该类返回具有适当分支方法的序列中的下一个类。这样做的好处是在模块的设计中隐含了代码的顺序,并且方法只能在适当的时间调用。这导致了许多类,大多数只使用一种没有分支的方法。我已经被警告说这是一种不良的设计模式。
是否有最佳方法来确保代码按特定顺序运行,并且从代码的设计中可以明显看出该顺序?
答案 0 :(得分:0)
您已经在使用通用方法来处理函数调用顺序:将令牌从一个函数传递到另一个函数:
token1 = func1(token0)
token2 = func2(token1)
token3 = func2(token2)
...
通常,令牌也是有用的结果。在您的示例中:如果没有transform_data
,您将不会呼叫data
:data
是token0
。因此,transform_data
将被称为之后 query_data
。
但是我认为您要处理的真正问题有所不同:您担心用户向下一个函数输入错误的输入,并且该函数可能会接受它并返回错误的结果:
我只是担心,在一个混乱的过程中,很难跟踪您是否有一个有效的输入...稀疏矩阵是对一种模型类型的无效输入,但对于另一种(comments)< / em>
在静态类型的语言中,它不会(通常)发生,因为如果您在希望有猫的情况下给兔子,该程序将无法编译。在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鼓励每个程序员负责。仅在需要时使用强制执行函数调用顺序的方法。