Ответ 1
Хорошо, вы могли бы сделать...
first,second = foo(True) or (None,None)
first,second = foo(False) or (None,None)
но насколько я знаю, нет более простого способа расширения None, чтобы заполнить всю кортеж.
Меня всегда раздражает этот факт:
$ 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)) или предложение о лучшей стратегии проектирования для таких случаев, как тот, который я представляю? * переменные могут быть?
Хорошо, вы могли бы сделать...
first,second = foo(True) or (None,None)
first,second = foo(False) or (None,None)
но насколько я знаю, нет более простого способа расширения None, чтобы заполнить всю кортеж.
Я не вижу, что не так с возвращением (None, None). Он намного чище, чем предлагаемые здесь решения, которые включают гораздо больше изменений в ваш код.
Также не имеет смысла, что вы хотите, чтобы None автоматически разбивался на 2 переменные.
Я думаю, что существует проблема абстракции.
Функция должна поддерживать некоторый уровень абстракции, что помогает уменьшить сложность кода.
В этом случае либо функция не поддерживает правильную абстракцию, либо вызывающий не соблюдает ее.
Функция могла бы быть чем-то вроде get_point2d()
; в этом случае уровень абстракции находится на кортеже, и поэтому возвращение None было бы хорошим способом оповестить какой-либо конкретный случай (например, несуществующий объект). Ошибка в этом случае должна была бы ожидать два элемента, в то время как на самом деле единственное, что вы знаете, это то, что функция возвращает один объект (с информацией, связанной с точкой 2d).
Но это могло бы быть нечто вроде get_two_values_from_db()
; в этом случае абстракция будет нарушена возвратом None, потому что функция (как подсказывает название) должна возвращать два значения, а не один!
В любом случае основная цель использования функции - уменьшения сложности - по крайней мере частично утрачена.
Обратите внимание, что этот вопрос не будет явно отображаться с оригинальным именем; что также важно всегда давать хорошие имена функциям и методам.
Я не думаю, что там трюк. Вы можете упростить код вызова:
values = foo(False)
if values:
first, second = values
или даже:
values = foo(False)
first, second = values or (first_default, second_default)
где first_default и second_default - значения, которые вы давали бы первым и вторым по умолчанию.
Как насчет этого:
$ 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
. Я очень удивлен, что никто больше не думал об этом. (Или, если они есть, я хотел бы знать, почему они не использовали его.)
Вы должны быть осторожны с стилем решения 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))
def foo(flag):
return ((1,2) if flag else (None, None))
ОК, я просто вернусь (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
Я нашел решение этой проблемы:
Возвращает 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 лет спустя, если вы хотите использовать значения по умолчанию, я не думаю, что есть лучший способ, чем уже предоставленный:
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