Как обеспечить выполнение функций в определенном порядке? - PullRequest
0 голосов
/ 28 марта 2019

Я относительно новичок в шаблонах проектирования 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 голосов
/ 28 марта 2019

Вы уже используете общий метод для обработки порядка функций: передать токен из функции в другую:

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 поощряет каждого программиста к ответственности. Используйте методы, которые обеспечивают порядок вызовов функций, только если это необходимо.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...