How to return multiple values from a function?
Канонический способ вернуть несколько значений в языках, которые его поддерживают, часто кортежем.
Вариант: Использование кортежа
Рассмотрим этот тривиальный пример:
def f(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return (y0,y1,y2)
Однако это быстро становится проблематичным, так как количество возвращаемых значений увеличивается. Что если вы хотите вернуть четыре или пять значений? Конечно, вы можете продолжать их использовать, но легко забыть, какое значение где. Также довольно уродливо распаковывать их там, где вы хотите их получить.
Вариант: Использование словаря
Следующий логический шаг, по-видимому, состоит в том, чтобы ввести своего рода "запись записи". В Python очевидный способ сделать это с помощью dict
,
Учтите следующее:
def g(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return {'y0':y0, 'y1':y1 ,'y2':y2 }
(edit- Просто чтобы прояснить, y0, y1 и y2 просто подразумеваются как абстрактные идентификаторы. Как уже отмечалось, на практике вы будете использовать значимые идентификаторы)
Теперь у нас есть механизм, с помощью которого мы можем проецировать конкретного члена возвращаемого объекта. Например,
result['y0']
Вариант: Использование класса
Однако есть и другой вариант. Вместо этого мы могли бы вернуть специализированную структуру. Я описал это в контексте Python, но уверен, что это применимо и к другим языкам. Действительно, если вы работали в C, это вполне может быть вашим единственным вариантом. Вот оно:
class ReturnValue(object):
def __init__(self, y0, y1, y2):
self.y0 = y0
self.y1 = y1
self.y2 = y2
def g(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return ReturnValue(y0, y1, y2)
В Python два предыдущих, возможно, очень похожи с точки зрения сантехники- В конце концов { y0, y1, y2 }
просто в конечном итоге записи во внутреннем __dict__
из ReturnValue
,
Есть еще одна особенность, предоставляемая Python, хотя для крошечных объектов, __slots__
приписывать. Класс может быть выражен как:
class ReturnValue(object):
__slots__ = ["y0", "y1", "y2"]
def __init__(self, y0, y1, y2):
self.y0 = y0
self.y1 = y1
self.y2 = y2
Из Справочного руководства по Python:
__slots__
Объявление принимает последовательность переменных экземпляра и резервирует достаточно места в каждом экземпляре для хранения значения для каждой переменной. Пространство сэкономлено, потому что__dict__
не создается для каждого экземпляра.
Вариант: использование списка
Другое предположение, которое я упустил, исходит от Билла Ящера:
def h(x):
result = [x + 1]
result.append(x * 3)
result.append(y0 ** y3)
return result
Это мой самый нелюбимый метод. Я предполагаю, что я испорчен воздействием Haskell, но идея списков смешанного типа всегда чувствовала себя неудобно для меня. В этом конкретном примере список является типом -не-смешанным, но это возможно. Насколько я могу судить, используемый таким образом список действительно ничего не дает по отношению к кортежу. Единственная реальная разница между списками и кортежами в Python заключается в том, что списки являются изменяемыми, а кортежи - нет. Лично я склонен переносить условные обозначения из функционального программирования: использовать списки для любого количества элементов одного типа и кортежи для фиксированного числа элементов заранее определенных типов.
Вопрос
После длительной преамбулы встает неизбежный вопрос. Какой метод (как вы думаете) лучше?
Я обычно ловил себя на пути к словарю, потому что он требует меньше работы по настройке. Однако, с точки зрения типов, вам лучше пойти по пути класса, так как это поможет вам избежать путаницы в том, что представляет собой словарь. С другой стороны, есть некоторые в сообществе Python, которые считают, что подразумеваемые интерфейсы должны быть предпочтительнее явных интерфейсов, и в этот момент тип объекта на самом деле не имеет значения, поскольку вы в основном полагаетесь на соглашение, согласно которому тот же атрибут всегда будет иметь одинаковое значение.
Итак, как -you- вернуть несколько значений в Python?
14 ответов
Именованные кортежи были добавлены в 2.6 для этой цели. Также смотрите os.stat для аналогичного встроенного примера.
>>> import collections
>>> Point = collections.namedtuple('Point', ['x', 'y'])
>>> p = Point(1, y=2)
>>> p.x, p.y
1 2
>>> p[0], p[1]
1 2
В последних версиях Python 3 (3.6+, я думаю), новый typing
библиотека получила NamedTuple
Класс, чтобы сделать именованные кортежи проще и мощнее. Наследование от typing.NamedTuple
позволяет использовать строки документов, значения по умолчанию и аннотации типов.
Пример (из документов):
class Employee(NamedTuple): # inherit from collections.NamedTuple
name: str
id: int = 3 # default value
employee = Employee('Guido')
assert employee.id == 3
Для небольших проектов мне проще всего работать с кортежами. Когда это становится слишком сложным для управления (и не раньше), я начинаю группировать вещи в логические структуры, однако я думаю, что предлагаемое вами использование словарей и объектов ReturnValue является неправильным (или слишком упрощенным).
Возвращение словаря с ключами y0, y1, y2 и т. Д. Не дает никаких преимуществ перед кортежами. Возвращение экземпляра ReturnValue со свойствами.y0 .y1 .y2 и т. Д. Также не дает никаких преимуществ перед кортежами. Вам нужно начать называть вещи, если вы хотите получить что-нибудь, и вы можете сделать это с помощью кортежей в любом случае:
def getImageData(filename):
[snip]
return size, (format, version, compression), (width,height)
size, type, dimensions = getImageData(x)
ИМХО, единственная хорошая техника помимо кортежей - возвращать реальные объекты с правильными методами и свойствами, как вы получаете из re.match()
или же open(file)
,
Многие ответы предполагают, что вам нужно вернуть какую-то коллекцию, например, словарь или список. Вы можете оставить дополнительный синтаксис и просто записать возвращаемые значения через запятую. Примечание: это технически возвращает кортеж.
def f():
return True, False
x, y = f()
print(x)
print(y)
дает:
True
False
Я голосую за словарь.
Я обнаружил, что если я сделаю функцию, которая возвращает что-то больше, чем 2-3 переменных, я сверну их в словаре. В противном случае я склонен забывать порядок и содержание того, что я возвращаю.
Кроме того, введение "специальной" структуры делает ваш код более сложным для понимания. (Кому-то еще придется искать код, чтобы узнать, что это такое)
Если вас беспокоит поиск типа, используйте описательные ключи словаря, например, "список значений x".
def g(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return {'y0':y0, 'y1':y1 ,'y2':y2 }
Другой вариант будет использовать генераторы:
>>> def f(x):
y0 = x + 1
yield y0
yield x * 3
yield y0 ** 4
>>> a, b, c = f(5)
>>> a
6
>>> b
15
>>> c
1296
Хотя кортежи IMHO обычно лучше, за исключением случаев, когда возвращаемые значения являются кандидатами для инкапсуляции в классе.
Я предпочитаю
def g(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return {'y0':y0, 'y1':y1 ,'y2':y2 }
кажется, все остальное просто дополнительный код, чтобы сделать то же самое.
Я предпочитаю использовать кортежи, когда они кажутся "естественными"; координаты являются типичным примером, когда отдельные объекты могут стоять самостоятельно, например, в одноосных вычислениях только масштабирования, и порядок важен. Примечание: если я могу сортировать или перетасовывать элементы без ущерба для значения группы, то, вероятно, мне не следует использовать кортеж.
Я использую словари в качестве возвращаемого значения только тогда, когда сгруппированные объекты не всегда одинаковы. Подумайте необязательные заголовки электронной почты.
В остальных случаях, когда сгруппированные объекты имеют внутреннее значение внутри группы или нужен полноценный объект со своими собственными методами, я использую класс.
>>> def func():
... return [1,2,3]
...
>>> a,b,c = func()
>>> a
1
>>> b
2
>>> c
3
Как правило, "специализированная структура" фактически представляет собой разумное текущее состояние объекта со своими собственными методами.
class Some3SpaceThing(object):
def __init__(self,x):
self.g(x)
def g(self,x):
self.y0 = x + 1
self.y1 = x * 3
self.y2 = y0 ** y3
r = Some3SpaceThing( x )
r.y0
r.y1
r.y2
Я люблю находить имена для анонимных структур, где это возможно. Значимые имена делают вещи более понятными.
Кортежи, диктовки и объекты Python предлагают программисту плавный компромисс между формальностью и удобством для небольших структур данных ("вещей"). Для меня выбор того, как представлять вещь, продиктован главным образом тем, как я собираюсь использовать структуру. В C++ распространено соглашение struct
для элементов только для данных и class
для объектов с методами, даже если вы можете на законных основаниях struct
; моя привычка похожа на Python, с dict
а также tuple
на месте struct
,
Для наборов координат я буду использовать tuple
а не точка class
или dict
(и обратите внимание, что вы можете использовать tuple
как ключ словаря, так dict
создавать большие разреженные многомерные массивы).
Если я собираюсь перебирать список вещей, я предпочитаю распаковывать tuple
на итерации:
for score,id,name in scoreAllTheThings():
if score > goodScoreThreshold:
print "%6.3f #%6d %s"%(score,id,name)
... так как версия объекта более загромождена, чтобы читать:
for entry in scoreAllTheThings():
if entry.score > goodScoreThreshold:
print "%6.3f #%6d %s"%(entry.score,entry.id,entry.name)
... не говоря уже о dict
,
for entry in scoreAllTheThings():
if entry['score'] > goodScoreThreshold:
print "%6.3f #%6d %s"%(entry['score'],entry['id'],entry['name'])
Если эта вещь широко используется, и вы обнаруживаете, что выполняете аналогичные нетривиальные операции над ней в нескольких местах кода, то обычно стоит сделать ее объектом класса с соответствующими методами.
Наконец, если я собираюсь обмениваться данными с компонентами системы, отличными от Python, я чаще всего буду хранить их в dict
потому что это лучше всего подходит для сериализации JSON.
+1 по предложению С. Лотта именованного контейнерного класса.
Для Python 2.6 и выше именованный кортеж обеспечивает удобный способ простого создания этих контейнерных классов, и результаты "легковесны и требуют не больше памяти, чем обычные кортежи".
"Лучший" является частично субъективным решением. Используйте кортежи для небольших возвращаемых множеств в общем случае, когда неизменяемым является приемлемым. Кортеж всегда предпочтительнее списка, когда изменчивость не является обязательной.
Для более сложных возвращаемых значений или для случая, когда важна формальность (т. Е. Код высокого значения), лучше использовать именованный кортеж. Для самого сложного случая объект обычно лучший. Тем не менее, это действительно ситуация, которая имеет значение. Если имеет смысл возвращать объект, потому что это то, что у вас естественным образом есть в конце функции (например, шаблон Factory), тогда верните объект.
Как сказал мудрец:
Преждевременная оптимизация - корень всего зла (или, по крайней мере, большей его части) в программировании.
Я бы использовал dict для передачи и возврата значений из функции:
Используйте переменную форму, как определено в форме.
form = {
'level': 0,
'points': 0,
'game': {
'name': ''
}
}
def test(form):
form['game']['name'] = 'My game!'
form['level'] = 2
return form
>>> print(test(form))
{u'game': {u'name': u'My game!'}, u'points': 0, u'level': 2}
Это самый эффективный способ для меня и для процессорного блока.
Вы должны передать только один указатель и вернуть только один указатель.
Вам не нужно менять аргументы функций (их тысячи) всякий раз, когда вы вносите изменения в свой код.
В таких языках, как Python, я обычно использую словарь, так как он требует меньше затрат, чем создание нового класса.
Однако, если я обнаружу, что постоянно возвращаю один и тот же набор переменных, то это, вероятно, связано с новым классом, который я выделю.