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

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

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

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

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?

+944
10 дек. '08 в 1:55
источник поделиться
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
+600
10 дек. '08 в 16:36
источник

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

+196
10 дек. '08 в 2:22
источник

Связанные вопросы


Похожие вопросы

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

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

дает:

True
False
+180
14 апр. '16 в 20:08
источник

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

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

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

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

def g(x):
  y0 = x + 1
  y1 = x * 3
  y2 = y0 ** y3
  return {'y0':y0, 'y1':y1 ,'y2':y2 }
+66
10 дек. '08 в 2:42
источник

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

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

+36
23 февр. '14 в 15:26
источник

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

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

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

+28
10 дек. '08 в 2:40
источник

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

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

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

+26
10 дек. '08 в 2:00
источник
>>> def func():
...    return [1,2,3]
...
>>> a,b,c = func()
>>> a
1
>>> b
2
>>> c
3
+25
21 янв. '15 в 20:57
источник

Кортежи, 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.

+18
13 авг. '13 в 20:18
источник

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

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

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

+18
10 дек. '08 в 2:15
источник

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

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

+16
10 дек. '08 в 3:51
источник

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

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

+4
10 дек. '08 в 2:02
источник

Я бы использовал 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}

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

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

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

+3
30 окт. '17 в 14:01
источник

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

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

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

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

+1
29 авг. '18 в 19:30
источник

Посмотрите другие вопросы по меткам или Задайте вопрос