Возврат Нет или кортеж и распаковка

Меня всегда раздражает этот факт:

$ 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

Какой вид раздражает. Есть ли уловка для улучшения этой ситуации (например, чтобы установить как первый, так и второй на None без возврата foo (None, None)) или предложение о лучшей стратегии проектирования для таких случаев, как тот, который я представляю? * переменные могут быть?

Ответы

Ответ 1

Хорошо, вы могли бы сделать...

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

но насколько я знаю, нет более простого способа расширения None, чтобы заполнить всю кортеж.

Ответ 2

Я не вижу, что не так с возвращением (None, None). Он намного чище, чем предлагаемые здесь решения, которые включают гораздо больше изменений в ваш код.

Также не имеет смысла, что вы хотите, чтобы None автоматически разбивался на 2 переменные.

Ответ 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 - значения, которые вы давали бы первым и вторым по умолчанию.

Ответ 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. Я очень удивлен, что никто больше не думал об этом. (Или, если они есть, я хотел бы знать, почему они не использовали его.)

Ответ 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), вот путь, использующий подкласс кортежа. В противном случае вы не возвращаете 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 not None, but it an empty container.

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

Вывод является желаемым:

1 2
None None

Ответ 9

Я нашел решение этой проблемы:

Возвращает None или возвращает объект.

Однако вы не хотите писать класс только для возврата объекта. Для этого вы можете использовать named tuple

Вот так:

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

Более 10 лет спустя, если вы хотите использовать значения по умолчанию, я не думаю, что есть лучший способ, чем уже предоставленный:

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

Однако, если вы хотите пропустить случай, когда возвращается None, начиная с Python 3.8, вы можете использовать оператор моржа (т.е. выражения присваивания) - также обратите внимание упрощенный foo:

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

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

Вы можете использовать ветку else, чтобы назначать значения по умолчанию, которые хуже, чем предыдущая опция or.

К сожалению, оператор моржа не поддерживает кортежи без скобок, поэтому это всего лишь выигрыш в одну строку по сравнению с:

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