如何确保功能按特定顺序运行?

时间:2019-03-28 00:04:30

标签: python function class design-patterns

我对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)

每个功能必须按此顺序运行。在此特定示例中,顺序应该非常明显。但是在更复杂的模块中,可能会有分支路径,并且如果没有大量的文档应该以什么顺序运行这些函数,可能会不清楚。

只需将模块包装在另一个按顺序应用功能的功能中就足够简单了,但这似乎使不必要的复杂功能带有许多违反单一职责的参数。

我还尝试将每个函数包装在一个类中,该类返回具有适当分支方法的序列中的下一个类。这样做的好处是在模块的设计中隐含了代码的顺序,并且方法只能在适当的时间调用。这导致了许多类,大多数只使用一种没有分支的方法。我已经被警告说这是一种不良的设计模式。

是否有最佳方法来确保代码按特定顺序运行,并且从代码的设计中可以明显看出该顺序?

1 个答案:

答案 0 :(得分:0)

您已经在使用通用方法来处理函数调用顺序:将令牌从一个函数传递到另一个函数:

token1 = func1(token0)
token2 = func2(token1)
token3 = func2(token2)
...

通常,令牌也是有用的结果。在您的示例中:如果没有transform_data,您将不会呼叫datadatatoken0。因此,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鼓励每个程序员负责。仅在需要时使用强制执行函数调用顺序的方法。