【问题标题】:Returning intermediate results from function in Python从 Python 中的函数返回中间结果
【发布时间】:2014-02-26 06:21:32
【问题描述】:

假设我有一个 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 中有任何返回 dicts 的函数,所以这似乎不是一个好主意。 (为什么?)我也经常需要输入sumvars(x,y,z)['final']作为默认返回值......

我看到的另一个选项是创建全局变量,但在我的模块中包含一堆变量似乎是错误的,我需要记住它们的名称,而且不附加到函数本身看起来像是一个糟糕的设计选择。

对于这种情况,正确的功能设计是什么?

【问题讨论】:

  • 问题是你打算如何使用这个功能。返回值的字典并没有错,但这意味着任何使用此函数的人都必须知道这一点并知道如何使用返回的字典。
  • 如果您要大量使用一些中间结果,那么最好为此创建一个单独的函数。
  • 在你的情况下,为什么不使用列表来存储中间结果呢?
  • @JayanthKoushik 这仍然需要对同一事物进行两次评估。
  • @zhangxaochen List, tuple, dict, 不管在这种情况下都是同一个概念。 Dict 更好,因为它具有事物的名称,因此更好的可读性。

标签: python function


【解决方案1】:

通常,当您有两种不同的返回数据方式时,请继续制作两个不同的函数。毕竟,“平面比嵌套好”。只需一个呼叫另一个,这样您就不会重复自己。

例如,在标准库中,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)

(我更喜欢从我的 API 中返回 namedtuples 而不是 dicts,它更漂亮)

另一个明显的例子是rere.findall是它自己的函数,而不是search的一些配置标志。

现在,标准库是由许多作者制作的庞大的东西,因此您会找到每个示例的反例。不过,您会更频繁地看到上述模式,而不是一个接受一些配置标志的综合函数,而且我发现它更具可读性。

【讨论】:

    【解决方案2】:

    按照 Jayanth Koushik 的建议,将通用计算放入自己的函数中,前提是该计算可以适当命名。如果您想从单个函数返回许多值(中间结果和最终结果),那么根据您的目标,dict 可能是一种过度杀伤,但在 python 中,如果您的函数有,则简单地返回一个元组会更自然要返回的值很多:

    def myfunc():
        intermediate = 5
        result = 6
        return intermediate, result
    
    # using the function:
    intermediate, result = myfunc()
    

    【讨论】:

      【解决方案3】:

      不确定函数属性是否是个好主意:

      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 提到的,这个想法就像全局变量,你之前计算的“中间结果”在你想重用它们时无法存储。

      【讨论】:

      • 哇!认为它甚至不会编译,但从可用性的角度来看似乎很理想。
      • 在函数的 __dict__ 中走私数据总是很可爱,但我强烈建议不要在真正的 API 中这样做。
      • 我会说这是一个坏主意,原因与全局变量或多或少相同。对函数的每次调用都会踩到以前存储的值,因此如果您从调用堆栈中的多个位置调用它,它很快就会变得混乱。
      • @BrenBarn 是的,它就像全局变量......它就是我想出来的。
      • 我不认为使用方法属性是一个好习惯,因为它们几乎就像使用全局变量一样。在此示例中, sumvars.first_step 将在有人调用 sumvars 方法时更改。
      【解决方案4】:

      刚刚想出了这个想法,这可能是一个更好的解决方案:

      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
      

      【讨论】:

        【解决方案5】:

        我相信正确的解决方案是使用一个类,以便更好地掌握您正在建模的内容。例如,在矩阵的情况下,您可以简单地将行列式存储在“行列式”属性中。

        这是一个使用您的矩阵示例的示例。

        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
        

        这还允许您将方法分成更简单的方法,例如用于计算矩阵行列式的方法。

        【讨论】:

        • 这就像返回一个dict,只是语法略有不同,并且需要在使用前实例化一个类。
        • 不完全是,我认为主要区别在于,一个类可以让您以结构化的方式对问题进行反建模。例如,使用“Circle”类生成具有您要计算一次的属性的圆;面积、周长等
        • 嗯,这是一种通过将全局变量包装到单独的类中来避免全局变量。问题是我需要创建我并不真正想要的类。这将是一个很好的解决方案,否则,我当然会经常做这些事情......
        【解决方案6】:

        另一种变化:

        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 而不存储中间值。但是电话

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

        返回相同的答案6并且将中间计算插入到提供的字典中。

        【讨论】:

          【解决方案7】:

          选项 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
          >>>
          

          【讨论】:

          • 好吧,那么要返回最终值,我需要多次调用函数......并且还要记住我应该为每件事调用多少次,似乎很难用这个。如果我添加另一个中间结果怎么办?我现有的所有代码都中断了...
          • 参考我的矩阵示例。想象一下,我想返回的中间东西很少。 1.如果我不想要中间结果,我如何调用函数? 2.我如何访问特定的中间结果,即'行列式',我必须记住这个结果的编号吗?
          • 有很多方法可以做你想做的事。在您的情况下,我认为您的函数最好返回 dicttuple 或具有包含所有结果的属性的对象。
          【解决方案8】:

          受@zhangxaochen 解决方案的启发,这是我使用类属性对您的问题的看法:

          class MyClass():
              def __init__(self):
                  self.i = 4
          
              def f(self):
                  s = self.i
                  MyClass.first_step = s
                  print(MyClass.first_step)
          
                  s += self.i
                  MyClass.second_step = s
                  print(MyClass.second_step)
          
                  s += self.i
                  return s
          
          def main():
              x = MyClass()
              print(x.f()) # print final s
              print(x.first_step)
              print(x.second_step)
              print(MyClass.second_step)
          

          注意:我包括了几个打印件,以更明确地说明如何检索属性值。

          结果:

          4
          8
          12
          4
          8
          8
          

          【讨论】:

            猜你喜欢
            • 2013-01-05
            • 1970-01-01
            • 2012-12-19
            • 2012-12-18
            • 1970-01-01
            • 1970-01-01
            • 2016-06-10
            • 1970-01-01
            • 2011-11-18
            相关资源
            最近更新 更多