Возврат промежуточных результатов из функции в Python

представьте, что у меня есть модуль Python с некоторой функцией в нем:

def sumvars(x, y, z):
    s = x
    s += y
    s += z
    return s

но иногда я хочу получить результаты некоторых промежуточных вычислений (например, у меня может быть функция, которая меняет матрицу и хотела бы знать определитель, который был вычислен как промежуточный шаг). Очевидно, я не хотел бы снова переделывать эти вычисления, если они уже были сделаны в этой функции.

моя первая идея-вернуть дикт:

def sumvars(x, y, z):
    d = {}
    s = x
    d['first_step'] = s
    s += y
    d['second_step'] = s
    s += z
    d['final'] = s
    return d

но я не помню никаких функций в numpy или scipy, которые возвращают дикты, и поэтому кажется, что это может быть не очень хорошая идея. (Почему?) Также обычно мне всегда придется печатать sumvars(x,y,z)['final'] для возвращаемого значения по умолчанию...

другой вариант, который я вижу, - это создание глобальных переменных, но кажется неправильным иметь кучу их в моем модуле, мне нужно будет запомнить их имена и, кроме того, не быть прикрепленным к самой функции, выглядит как плохой выбор дизайна.

что было бы правильной конструкцией функции для такой ситуации?

7 ответов


обычно, когда у вас есть два разных способа возврата данных, продолжайте и сделайте две разные функции. "Квартира лучше, чем вложенная", в конце концов. Просто позвоните друг другу, чтобы не повторяться.

например, в стандартной библиотеке, urllib.parse и parse_qs (который возвращает dict) и parse_qsl (который возвращает list). parse_qs как раз тогда звонит другой:

def parse_qs(...):

    parsed_result = {}
    pairs = parse_qsl(qs, keep_blank_values, strict_parsing,
                      encoding=encoding, errors=errors)
    for name, value in pairs:
        if name in parsed_result:
            parsed_result[name].append(value)
        else:
            parsed_result[name] = [value]
    return parsed_result

довольно проста. Так в вашем примере кажется прекрасно иметь

def sumvars(x, y, z):
    return sumvars_with_intermediates(x, y, z).final

def sumvars_with_intermediates(x, y, z):
    ...
    return my_namedtuple(final, first_step, second_step)

(Я за возвращение namedtuples вместо dicts от моего APIs, это просто красивее)

еще один очевидный пример-в re: re.findall своя собственная функция, не некоторый флаг конфигурации к search.

теперь стандартная библиотека-это растянутая вещь, сделанная многими авторами, поэтому вы найдете контрпримеры к каждому примеру. Вы гораздо чаще будете видеть вышеуказанный шаблон, а не одну функцию omnibus, которая принимает некоторые однако флаги конфигурации, и я нахожу его гораздо более читаемым.


поместите общий расчет в свою собственную функцию, как рекомендовал Jayanth Koushik, если этот расчет можно назвать соответствующим образом. Если вы хотите вернуть много значений (промежуточный результат и конечный результат) из одной функции, то дикт может быть излишним в зависимости от вашей цели, но в python гораздо более естественно просто вернуть кортеж, если ваша функция имеет много значений для возврата:

def myfunc():
    intermediate = 5
    result = 6
    return intermediate, result

# using the function:
intermediate, result = myfunc()

Не уверен, что атрибуты функций-хорошая идея:

In [569]: def sumvars(x, y, z):
     ...:     s = x
     ...:     sumvars.first_step = s
     ...:     s += y
     ...:     sumvars.second_step = s
     ...:     s += z
     ...:     return s


In [570]: res=sumvars(1,2,3)
     ...: print res, sumvars.first_step, sumvars.second_step
     ...: 
6 1 3

Примечание: как упоминал @BrenBarn, эта идея так же, как глобальные переменные, ранее рассчитанных "промежуточные результаты" не могут быть сохранены, когда вы хотите использовать их.


просто придумал эту идею, которая может быть лучшим решением:

def sumvars(x, y, z, mode = 'default'):
    d = {}
    s = x
    d['first_step'] = s
    s += y
    d['second_step'] = s
    s += z
    d['final'] = s
    if mode == 'default':
        return s
    else:
        return d

Я считаю, что правильное решение-использовать класс, чтобы лучше понять, что вы моделируете. Например, в случае матрицы вы можете просто сохранить определитель в атрибуте "определитель".

вот пример использования примера матрицы.

class Matrix:
    determinant = 0

    def calculate_determinant(self):
        #calculations
        return determinant

    def some_method(self, args):
       # some calculations here

       self.determinant = self.calculate_determinant()

       # other calculations

 matrix = Matrix()
 matrix.some_method(x, y, z)
 print matrix.determinant

Это также позволяет разделить ваш метод на более простые методы, например, для вычисления определителя вашей матрицы.


другой вариант:

def sumvars(x, y, z, d=None):
  s = x
  if not d is None:
    d['first_step'] = s
  s += y
  if not d is None:
    d['second_step'] = s
  s += z
  return s

функция всегда возвращает нужное значение без упаковки его в кортеж или словарь. Промежуточных результатов по-прежнему доступны, но только по запросу. Зов

sumvars(1, 2, 3)

просто возвращает 6 без хранения промежуточных значений. Но зов! .. --6-->

d = {}
sumvars(1, 2, 3, d)

возвращает один и тот же ответ 6 и вставляет промежуточные вычисления в исходном словаре.


1. сделать две отдельные функции.

2. использовать генератор:

>>> def my_func():
...     yield 1
...     yield 2
... 
>>> result_gen = my_func()
>>> result_gen
<generator object my_func at 0x7f62a8449370>
>>> next(result_gen)
1
>>> next(result_gen)
2
>>> next(result_gen)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>