Вы уже используете общий метод для обработки порядка функций: передать токен из функции в другую:
token1 = func1(token0)
token2 = func2(token1)
token3 = func2(token2)
...
Обычно токены также являются полезными результатами. В вашем примере: вы не позвоните transform_data
, если у вас нет data
, чтобы дать ему: data
- это token0
. Таким образом, transform_data
будет называться после query_data
.
Но я думаю, что реальная проблема, с которой вы сталкиваетесь, другая: вы боитесь, что пользователь неверно введет следующую функцию и что функция может принять ее и вернуть неправильный результат:
Я просто обеспокоен тем, что в запутанном процессе было бы трудно отследить, есть ли у вас действительный ввод ... Разреженная матрица является недопустимым вводом для одного типа модели, но действительна для другого (комментарии)
В статически типизированных языках этого не произойдет (как правило), потому что программа не будет компилироваться, если вы дадите кролика, когда ожидается кот. В python, с вводом утки, это не так просто. Давайте посмотрим, как вы можете применить это:
- хорошая документация
- проверить входные данные
- сгруппировать функции
- заблокировать некоторые параметры
Хорошая документация
Это, безусловно, лучшее решение. Каждый здесь взрослый. Не беспокойся слишком сильно.
Вы можете использовать Python способности печатать для информации о человеке.
Проверьте входные данные
Это вариант Защитное программирование . При необходимости оберните функции, которые вам не принадлежат:
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 поощряет каждого программиста к ответственности. Используйте методы, которые обеспечивают порядок вызовов функций, только если это необходимо.