Вопрос-Ответ

Alternatives for returning multiple values from a Python function [closed]

Альтернативы для возврата нескольких значений из функции Python [закрыты]

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

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

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

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)

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

Еще одно предложение, которое я упустил из виду, исходит от Bill the Lizard:

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

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

Список, используемый таким образом, на самом деле ничего не выигрывает по отношению к кортежу, насколько я могу судить. Единственное реальное различие между списками и кортежами в 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 typing.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 }
2023-08-17 15:33 python