Как вернуть несколько значений из функции?

Канонический способ вернуть несколько значений в языках, которые его поддерживают, часто кортеж.

Вариант: использование кортежа

Рассмотрим этот тривиальный пример:

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}

(Для ясности, y0, y1 и y2 означают только абстрактные идентификаторы. Как уже отмечалось, на практике вы будете использовать значимые идентификаторы.)

Теперь у нас есть механизм, с помощью которого мы можем проецировать конкретного члена возвращаемого объекта. Например,

result['y0']

Вариант: использование класса

Однако есть и другой вариант. Вместо этого мы могли бы вернуть специализированную структуру. Я описал это в контексте Python, но я уверен, что это применимо и к другим языкам. Действительно, если вы работали в C, это вполне может быть вашим единственным вариантом. Здесь идет:

class ReturnValue:
  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__ создается не для каждого экземпляра.

Вариант: использование класса данных (Python 3. 7+)

Используя новые классы данных Python 3.7, верните класс с автоматически добавленными специальными методами, набором текста и другими полезными инструментами:

@dataclass
class Returnvalue:
    y0: int
    y1: float
    y3: int

def total_cost(x):
    y0 = x + 1
    y1 = x * 3
    y2 = y0 ** y3
    return ReturnValue(y0, y1, y2)

Вариант: использование списка

Еще одно предложение, которое я упустил, исходит от Билла Ящерицы:

def h(x):
  result = [x + 1]
  result.append(x * 3)
  result.append(y0 ** y3)
  return result

Это мой наименее любимый метод, хотя. Я предполагаю, что я испорчен воздействием Haskell, но идея списков смешанного типа всегда чувствовала себя неудобно для меня. В этом конкретном примере список имеет тип -не-смешанный, но, возможно, это может быть.

Насколько я могу судить, используемый таким образом список действительно ничего не дает в отношении кортежа. Единственная реальная разница между списками и кортежами в Python заключается в том, что списки являются изменяемыми, а кортежи - нет.

Лично я склонен переносить условные обозначения из функционального программирования: использовать списки для любого количества элементов одного типа и кортежи для фиксированного числа элементов заранее определенных типов.

Вопрос

После длинной преамбулы встает неизбежный вопрос. Какой метод (как вы думаете) лучше?

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

С другой стороны, есть некоторые в сообществе Python, которые считают, что подразумеваемые интерфейсы должны быть предпочтительнее явных интерфейсов, и в этот момент тип объекта действительно не имеет значения, поскольку вы в основном полагаетесь на соглашение о том, что один и тот же атрибут всегда будет иметь одинаковое значение.

Итак, как вы можете вернуть несколько значений в Python?

Ответ 1

Именованные кортежи были добавлены в 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

Ответ 2

Для небольших проектов мне проще всего работать с кортежами. Когда это становится слишком сложным для управления (и не раньше), я начинаю группировать вещи в логические структуры, однако я думаю, что предлагаемое вами использование словарей и объектов ReturnValue является неправильным (или слишком упрощенным).

Возврат словаря с ключами "y0", "y1", "y2" и т.д. Не дает никаких преимуществ перед кортежами. Возврат экземпляра ReturnValue со свойствами .y0, .y1, .y2 и т.д. Также не дает никаких преимуществ перед кортежами. Вам нужно начать называть вещи, если вы хотите получить что-нибудь, и вы можете сделать это с помощью кортежей в любом случае:

def get_image_data(filename):
    [snip]
    return size, (format, version, compression), (width,height)

size, type, dimensions = get_image_data(x)

ИМХО, единственная хорошая техника, кроме кортежей, - возвращать реальные объекты с надлежащими методами и свойствами, как вы получаете из re.match() или open(file).

Ответ 3

Многие ответы предполагают, что вам нужно вернуть какую-то коллекцию, например, словарь или список. Вы можете оставить дополнительный синтаксис и просто записать возвращаемые значения через запятую. Примечание: это технически возвращает кортеж.

def f():
    return True, False
x, y = f()
print(x)
print(y)

дает:

True
False

Ответ 4

Я проголосую за словарь.

Я нахожу, что если я создам функцию, которая возвращает что-то большее, чем 2-3 переменных, я скрою их в словаре. В противном случае я стараюсь забыть порядок и содержание того, что я возвращаю.

Кроме того, введение "специальной" структуры затрудняет выполнение вашего кода. (Кому-то придется искать код, чтобы узнать, что это такое)

Если вы заинтересованы в поиске типа, используйте дескриптивные словарные ключи, например, "список значений x".

def g(x):
  y0 = x + 1
  y1 = x * 3
  y2 = y0 ** y3
  return {'y0':y0, 'y1':y1 ,'y2':y2 }

Ответ 5

Другим вариантом будет использование генераторов:

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

Ответ 6

Я предпочитаю использовать кортежи, когда они кажутся "естественными"; координаты являются типичным примером, когда отдельные объекты могут стоять самостоятельно, например, в одноосных вычислениях только масштабирования, и порядок важен. Примечание: если я могу сортировать или перетасовывать элементы без ущерба для значения группы, то, вероятно, мне не следует использовать кортеж.

Я использую словари в качестве возвращаемого значения только тогда, когда сгруппированные объекты не всегда одинаковы. Подумайте дополнительные заголовки электронной почты.

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

Ответ 7

Я предпочитаю:

def g(x):
  y0 = x + 1
  y1 = x * 3
  y2 = y0 ** y3
  return {'y0':y0, 'y1':y1 ,'y2':y2 }

Кажется, все остальное - просто дополнительный код для того же.

Ответ 8

>>> def func():
...    return [1,2,3]
...
>>> a,b,c = func()
>>> a
1
>>> b
2
>>> c
3

Ответ 9

Как правило, "специализированная структура" на самом деле является разумным текущим состоянием объекта с его собственными методами.

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

Мне нравится находить имена анонимных структур, где это возможно. Значимые имена делают вещи более ясными.

Ответ 10

Кортежи, dicts и объекты Python предлагают программисту плавный компромисс между формальностью и удобством для небольших структур данных ( "вещи" ). Для меня выбор того, как представлять вещь, диктуется главным образом тем, как я буду использовать структуру. В С++ это обычное соглашение использовать 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.

Ответ 11

+1 о предложении S.Lott именованного контейнерного класса.

Для Python 2.6 и выше именованный кортеж предоставляет удобный способ простого создания этих контейнерных классов, и результаты "легковесны и требуют не больше памяти, чем обычные кортежи".

Ответ 12

В таких языках, как Python, я бы обычно использовал словарь, поскольку он включал меньше накладных расходов, чем создание нового класса.

Однако, если я постоянно возвращаю один и тот же набор переменных, то это, вероятно, связано с новым классом, который я буду учитывать.

Ответ 13

Я бы использовал dict для передачи и возврата значений из функции:

Использовать переменную форму, определенную в form.

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}

Это самый эффективный способ для меня и для обработки.

Вам нужно передать только один указатель и вернуть только один указатель.

Вам не нужно изменять аргументы функции (тысячи из них) всякий раз, когда вы вносите изменения в свой код.

Ответ 14

"Лучший" является частично субъективным решением. Используйте кортежи для небольших возвращаемых множеств в общем случае, когда неизменяемым является приемлемым. Кортеж всегда предпочтительнее списка, когда изменчивость не является обязательной.

Для более сложных возвращаемых значений или для случая, когда важна формальность (т.е. Код высокого значения), лучше использовать именованный кортеж. Для самого сложного случая объект обычно лучший. Тем не менее, это действительно ситуация, которая имеет значение. Если имеет смысл возвращать объект, потому что это то, что у вас естественным образом есть в конце функции (например, шаблон Factory), тогда верните объект.

Как сказал мудрец:

Преждевременная оптимизация - корень всего зла (или, по крайней мере, большей его части) в программировании.