【问题标题】:Returning None or a tuple and unpacking返回 None 或元组并解包
【发布时间】:2010-11-19 11:31:10
【问题描述】:

我总是对这个事实感到恼火:

$ cat foo.py
def foo(flag):
    if flag:
        return (1,2)
    else:
        return None

first, second = foo(True)
first, second = foo(False)

$ python foo.py
Traceback (most recent call last):
  File "foo.py", line 8, in <module>
    first, second = foo(False)
TypeError: 'NoneType' object is not iterable

事实是,为了顺利解压,我必须捕获 TypeError 或拥有类似的东西

values = foo(False)
if values is not None:
    first, second = values

这有点烦人。是否有改善这种情况的技巧(例如,在没有 foo 返回 (None, None) 的情况下将 first 和 second 都设置为 None )或关于我提出的案例的最佳设计策略的建议? *变量可能吗?

【问题讨论】:

    标签: python return-value


    【解决方案1】:

    好吧,你可以这样做......

    first,second = foo(True) or (None,None)
    first,second = foo(False) or (None,None)
    

    但据我所知,没有更简单的方法可以扩展 None 来填充整个元组。

    【讨论】:

    • ooooh...这我没有考虑到。非常“失败”的方法,但很聪明。
    • 我不同意它是 perl-ish。 x or y 是一种非常 Python 的方式来表达 (x ? x : y)(if x then x else y)(if not x then y) 或者你想解释它。
    • 好的,刚刚完成了“跟着唱”“whatever or die()”典型的 perl 短语。
    • 通常我不喜欢这种语法,因为存在意外 False-y 值的危险,但在这种情况下没关系。
    【解决方案2】:

    我看不出返回 (None,None) 有什么问题。它比这里建议的解决方案要干净得多,因为这里涉及的代码更改要多得多。

    您希望 None 自动拆分为 2 个变量也是没有意义的。

    【讨论】:

    • 我只是在想同样的事情。我同意这没有意义:从概念的角度来看,返回 None w.r.t. Nones 的元组可能不同(当然这取决于真实的例程。这里我们说的是模拟案例)
    • +1 -- 绝对 -- 让函数返回如此糟糕的结果组合并且必须在调用者中处理它是很奇怪的。
    • @Alex:嗯,这取决于。通常,您可能会遇到一些例程应该返回 x (其中 x = 不管)或 None (如果未找到)的情况。如果您知道 x 是一个元组,并且是两个对象的元组,这是一种特殊情况,但是如果未找到,则 x 或 None 的情况仍然有效。你解压它的事实是忘记了被调用的例程。
    • 但是你应该把返回值作为一个整体来对待——事实上它是一个你可以轻松解包的元组只是巧合。
    • 有时这两个值是联系在一起的,总是。考虑下面@rob 的讨论。为一个不存在的point2d写(None, None)有点可笑。另外,(None, 3) 在谈论 point2ds 时是什么意思?没有。那么为什么不称它为None 并完成呢?
    【解决方案3】:

    我认为抽象有问题。

    函数应保持一定程度的抽象,这有助于降低代码的复杂性。
    在这种情况下,要么函数没有维护正确的抽象,要么调用者不尊重它。

    函数可能类似于get_point2d();在这种情况下,抽象级别在元组上,因此返回 None 将是表示某些特定情况(例如不存在的实体)的好方法。这种情况下的错误是期望两个项目,而实际上您唯一知道的是该函数返回一个对象(带有与 2d 点相关的信息)。

    但它也可能是 get_two_values_from_db();在这种情况下,抽象将通过返回 None 来破坏,因为函数(顾名思义)应该返回两个值而不是一个

    无论哪种方式,使用函数的主要目标 - 降低复杂性 - 至少部分丢失了。

    请注意,使用原始名称时,此问题不会清楚地显示出来;这也是为什么给函数和方法起好名字总是很重要的原因。

    【讨论】:

      【解决方案4】:

      我不认为有什么技巧。您可以将调用代码简化为:

      values = foo(False)
      if values:
          first, second = values
      

      甚至:

      values = foo(False)
      first, second = values or (first_default, second_default)
      

      其中 first_default 和 second_default 是您赋予 first 和 second 作为默认值的值。

      【讨论】:

      • 第二个选项工作的python版本是哪个? v = (1,); x, y = v 或 (v, 2)
      【解决方案5】:

      这个怎么样:

      $ cat foo.py 
      def foo(flag):
          if flag:
              return (1,2)
          else:
              return (None,)*2
      
      first, second = foo(True)
      first, second = foo(False)
      

      编辑:需要明确的是,唯一的变化是将return None 替换为return (None,)*2。我非常惊讶没有其他人想到这一点。 (或者如果他们有,我想知道他们为什么不使用它。)

      【讨论】:

      • 因为 OP 明确表示“没有 foo 返回(无,无)”。 return (None,)*2return (None,None) 相同。
      • 你确定这是什么意思?我认为 OP 只是想一直避免 typing 冗长的表达式 return None, None。我的方法解决了这个问题。
      • 嗯,它当然不能完全解决它,但确实减少了它,并且对于给定大小的较长元组效果更好。
      【解决方案6】:

      您应该小心x or y 风格的解决方案。它们有效,但它们比您的原始规范更广泛。本质上,如果foo(True) 返回一个空元组() 怎么办?只要您知道可以将其视为(None, None),您就可以使用所提供的解决方案。

      如果这是一个常见的场景,我可能会编写一个实用函数,例如:

      # needs a better name! :)
      def to_tup(t):
          return t if t is not None else (None, None)
      
      first, second = to_tup(foo(True))
      first, second = to_tup(foo(False))
      

      【讨论】:

        【解决方案7】:
        def foo(flag):
            return ((1,2) if flag else (None, None))
        

        【讨论】:

          【解决方案8】:

          好的,我会返回 (None, None),但只要我们在 whacko-land (heh),这里有一种使用元组子类的方法。在 else 的情况下,你不返回 None,而是返回一个空容器,这似乎符合事物的精神。容器的“迭代器”在空时解包 None 值。无论如何演示迭代器协议...

          使用 v2.5.2 测试:

          class Tuple(tuple):
              def __iter__(self):
                  if self:
                      # If Tuple has contents, return normal tuple iterator...
                      return super(Tuple, self).__iter__()
                  else:
                      # Else return a bogus iterator that returns None twice...
                      class Nonerizer(object):
                          def __init__(self):
                              self.x=0
                          def __iter__(self):
                              return self
                          def next(self):
                              if self.x < 2:
                                  self.x += 1
                                  return None
                              else:
                                  raise StopIteration
                      return Nonerizer()
          
          
          def foo(flag):
              if flag:
                  return Tuple((1,2))
              else:
                  return Tuple()  # It's not None, but it's an empty container.
          
          first, second = foo(True)
          print first, second
          first, second = foo(False)
          print first, second
          

          输出是期望的:

          1 2
          None None
          

          【讨论】:

          • Nonerizer() 可以替换为itertools.repeat(None, 2)
          • @augurar 或只是iter((None, None)),让整个练习毫无意义。
          【解决方案9】:

          10 多年后,如果你想使用默认值,我认为没有比已经提供的更好的方法了:

          first, second = foo(False) or (first_default, second_default)
          

          但是,如果您想在返回 None 时跳过这种情况,从 Python 3.8 开始,您可以使用 walrus 运算符(即assignment expressions) - 另请注意简化的 @987654325 @:

          def foo(flag):
              return (1, 2) if flag else None
          
          if values := Foo(False):
              (first, second) = values
          

          您可以使用else 分支来分配比之前的or 选项更差的默认值。

          可悲的是,海象运算符does not support unparenthesized tuples 因此与以下相比仅是一条线的增益:

          values = foo(False)
          if values:
              first, second = values
          

          【讨论】:

            【解决方案10】:

            当您可以控制方法 foo 时,您可以使用一种机制来完全避免该问题,即更改原型以允许提供默认值。如果您正在包装状态但不能保证存在特定的元组值,则此方法有效。

            # self.r is for example, a redis cache
            
            # This method is like foo - 
            # it has trouble when you unpack a json serialized tuple
            def __getitem__(self, key):
                val = self.r.get(key)
                if val is None:
                    return None
                return json.loads(val)
            
            # But this method allows the caller to 
            # specify their own default value whether it be
            # (None, None) or an entire object
            def get(self, key, default):
                val = self.r.get(key)
                if val is None:
                    return default
                return json.loads(val)
            

            【讨论】:

              【解决方案11】:

              我找到了解决这个问题的方法:

              返回 None 或返回一个对象。

              但是,您不希望仅仅为了返回一个对象而编写一个类。为此,您可以使用 命名元组

              像这样:

              from collections import namedtuple
              def foo(flag):
              if flag:
                 return None
              else:
                  MyResult = namedtuple('MyResult',['a','b','c']
                  return MyResult._make([1,2,3])
              

              然后:

              result = foo(True) # result = True
              result = foo(False) # result = MyResult(a=1, b=2, c=3)
              

              您可以访问这样的结果:

              print result.a # 1
              print result.b # 2
              print result.c # 3
              

              【讨论】:

                猜你喜欢
                • 2019-05-27
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2018-04-26
                • 1970-01-01
                相关资源
                最近更新 更多