Python 返回None或tuple并解压缩

Python 返回None或tuple并解压缩,python,return-value,Python,Return Value,我总是对这个事实感到恼火: $ 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

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

$ 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
这有点烦人。是否有技巧来改善这种情况(例如,在不返回foo(无,无)的情况下同时设置first和second-to-None),或者对我所介绍的案例的最佳设计策略提出建议*变量可能吗?

嗯,你可以

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

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

我认为没有什么诀窍。您可以将调用代码简化为:

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的值。

我看不出返回(None,None)有什么错。它比这里建议的解决方案要干净得多,这些解决方案需要对代码进行更多的更改


您希望将任何变量自动拆分为两个变量也是没有意义的。

您应该小心使用
x或y
样式的解决方案。它们可以工作,但比您最初的规范要宽泛一些。本质上,如果
foo(True)
返回一个空元组
()
?只要您知道可以将其视为
(无,无)
,您就可以使用提供的解决方案

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

# 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))

我认为存在一个抽象的问题

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

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

但它也可能是类似于
从\u db()获取两个\u值的东西。
;在这种情况下,由于函数(顾名思义)应该返回两个值,而不是一个值,抽象将因返回None而中断

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

请注意,此版本不会与原始名称一起清晰显示;这也是为什么给函数和方法起个好名字总是很重要的原因。

这是怎么回事:

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

first, second = foo(True)
first, second = foo(False)
编辑:只是想澄清一下,唯一的更改是将
返回无
替换为
返回(无,)*2
。我非常惊讶,没有其他人想到这一点。(或者如果他们有,我想知道他们为什么不使用它。)

好的,我会返回(无,无),但只要我们在whacko land(呵呵),这里有一种使用tuple子类的方法。在else情况下,您不返回任何内容,而是返回一个空容器,这似乎符合事物的精神。容器的“迭代器”为空时不解压缩任何值。演示迭代器协议

使用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

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

返回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

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

first, second = foo(False) or (first_default, second_default)
但是,如果您想跳过返回
None
时的情况,从Python 3.8开始,您可以使用walrus操作符(即)-还要注意简化的
foo

def foo(flag):
    return (1, 2) if flag else None

if values := Foo(False):
    (first, second) = values
您可以使用
else
分支来指定比上一个
选项更差的默认值

可悲的是,海象算子只增加了一行,相比之下:

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

当您控制方法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)

哦哦。。。这我没有考虑。非常“perlish”的方法,但很聪明。我不认为它是perlish
x或y
是一种非常类似Python的说法,表示
(x?x:y)
(如果x,则x或y)
(如果不是x,则y)
,或者不管你想解释什么。好的,只是用“随声附和”的“无论什么或死亡”()典型的perl短语。通常我不喜欢这种语法,因为会有意外的假y值的危险,但在这种情况下,这没关系。我只是在考虑同样的问题。我同意这是没有意义的:从概念的角度来看,返回None w.r.t.一个Nones元组可能是不同的(当然这取决于实际的例程。这里我们讨论的是模拟案例)+1--绝对--让函数返回如此古怪的结果组合,并且必须在调用者中处理它是很奇怪的。@Alex:嗯,那要看情况。通常,您可以有一些例程,它们应该返回x(x=whatever),如果没有找到,则不返回。如果您知道x是一个元组,并且是一个由两个对象组成的元组,这是一个特例,但是如果没有找到,那么情况x或None仍然有效。解包的事实被调用的例程忽略了。但是,您应该将返回值作为一个整体来对待——它是一个可以轻松解包的元组,这只是巧合。有时这两个值总是绑定在一起。考虑下面的罗布的讨论。为一个不存在的point2d编写
(无,无)
有点可笑。另外,当谈论point2ds时,
(无,3)
意味着什么?没有什么。那么,为什么不称之为
None
并保持沉默呢
def foo(flag):
    return (1, 2) if flag else None

if values := Foo(False):
    (first, second) = values
values = foo(False)
if values:
    first, second = values
# 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)