Канонический способ вернуть несколько значений в языках, которые его поддерживают, часто кортежем .
Вариант: Использование кортежа
Рассмотрим этот тривиальный пример:
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 заключается в том, что списки являются изменяемыми , а кортежи - нет.
Лично я склонен переносить условные обозначения из функционального программирования: использовать списки для любого количества элементов одного типа и кортежи для фиксированного числа элементов предопределенных типов.
Вопрос
После длинной преамбулы встает неизбежный вопрос. Какой метод (как вы думаете) лучше?
источник
y3
, но если y3 не объявлен как глобальный, это может привести кNameError: global name 'y3' is not defined
простому использованию3
?y3
, которое будет делать то же самое.Ответы:
Именованные кортежи были добавлены в 2.6 для этой цели. Также смотрите os.stat для аналогичного встроенного примера.
В последних версиях Python 3 (я думаю, 3.6+) в новой
typing
библиотеке появилсяNamedTuple
класс, облегчающий создание именованных кортежей и более мощный. Наследование отtyping.NamedTuple
позволяет использовать строки документов, значения по умолчанию и аннотации типов.Пример (из документов):
источник
namedtuple
заключается в том, что для массовых результатов требуется меньший объем памяти (длинные списки кортежей, например результаты запросов к БД). Для отдельных элементов (если рассматриваемая функция вызывается не часто) словари и классы также хороши. Но именованные кортежи - это хорошее и приятное решение и в этом случае.namedtuple
определений (каждый вызов создает новое), созданиеnamedtuple
класса является относительно дорогостоящим как в ЦП, так и в памяти, и все определения классов по своей природе включают циклические ссылки (поэтому в CPython вы ожидаете циклического запуска GC чтобы их выпустили). Это также делает невозможным дляpickle
класса (и, следовательно, невозможно использовать экземпляры сmultiprocessing
в большинстве случаев). Каждое создание класса на моем 3.6.4 x64 потребляет ~ 0.337 мс и занимает чуть менее 1 КБ памяти, убивая любую экономию экземпляра.namedtuple
классов ; затраты на ЦП снижаются примерно в 4 раза , но они все еще примерно в 1000 раз превышают затраты на создание экземпляра, а стоимость памяти для каждого класса остается высокой (я ошибался в своем последнем комментарии о том, что «меньше 1 КБ») для класса,_source
как правило, он равен 1,5 КБ;_source
удаляется в 3,7, так что он, вероятно, ближе к первоначальной заявке, составляющей чуть менее 1 КБ на создание класса).Для небольших проектов мне проще всего работать с кортежами. Когда это становится слишком сложным для управления (и не раньше), я начинаю группировать вещи в логические структуры, однако я думаю, что вы предлагаете использовать словари и
ReturnValue
объекты неправильно (или слишком упрощенно).Возвратившись словарем с ключами
"y0"
,"y1"
,"y2"
и т.д. не дают никаких преимуществ перед кортежами. ВозвращениеReturnValue
экземпляра со свойствами.y0
,.y1
,.y2
и т.д. не дает никаких преимуществ по сравнению кортежей либо. Вам нужно начать называть вещи, если вы хотите получить что-нибудь, и вы можете сделать это с помощью кортежей в любом случае:ИМХО, единственная хорошая техника помимо кортежей - возвращать реальные объекты с надлежащими методами и свойствами, как вы получаете из
re.match()
илиopen(file)
.источник
size, type, dimensions = getImageData(x)
и(size, type, dimensions) = getImageData(x)
? То есть, имеет ли значение перенос левой части кортежного присваивания?(1)
является int while(1,)
или1,
является кортежем.Многие ответы предполагают, что вам нужно вернуть какую-то коллекцию, например, словарь или список. Вы можете исключить дополнительный синтаксис и просто записать возвращаемые значения через запятую. Примечание: это технически возвращает кортеж.
дает:
источник
type(f())
возвращается<class 'tuple'>
.tuple
аспект явным; это не очень важно, что вы возвращаетеtuple
, это идиома для возврата периода нескольких значений. По той же причине, по которой вы опускаете парены с идиомой подстановкиx, y = y, x
, множественной инициализациейx, y = 0, 1
и т. Д .; конечно, он делаетtuple
s под капотом, но нет никакой причины делать это явным, так какtuple
s не имеет никакого значения. Учебник по Python вводит множественные назначения задолго до того, как он затрагиваетtuple
s.=
является кортежем в Python с или без скобок вокруг них. Так что на самом деле здесь нет явного или неявного. a, b, c такой же кортеж, как (a, b, c). При возврате таких значений также нет создания кортежа «под капотом», потому что это просто простой кортеж. ОП уже упоминал кортежи, так что на самом деле нет разницы между тем, что он упомянул, и тем, что показывает этот ответ. НетЯ голосую за словарь.
Я обнаружил, что если я сделаю функцию, которая возвращает что-то больше 2-3 переменных, я сверну их в словаре. В противном случае я склонен забывать порядок и содержание того, что я возвращаю.
Кроме того, введение «специальной» структуры делает ваш код более сложным для понимания. (Кому-то еще придется поискать код, чтобы узнать, что это такое)
Если вас беспокоит поиск типа, используйте описательные ключи словаря, например, «список значений x».
источник
result = g(x); other_function(result)
Другой вариант будет использовать генераторы:
Хотя кортежи IMHO обычно лучше, за исключением случаев, когда возвращаемые значения являются кандидатами для инкапсуляции в классе.
источник
yield
?def f(x): …; yield b; yield a; yield r
vs. нет(g for g in [b, a, r])
, и оба они легко конвертируются в списки или кортежи и, следовательно, будут поддерживать распаковку кортежей. Форма генератора кортежей следует функциональному подходу, тогда как форма функции является обязательной и позволит управлять потоком и назначать переменные.Я предпочитаю использовать кортежи, когда они кажутся «естественными»; координаты являются типичным примером, когда отдельные объекты могут стоять самостоятельно, например, в одноосных вычислениях только масштабирования, и порядок важен. Примечание: если я могу сортировать или перетасовывать элементы без ущерба для значения группы, то, вероятно, мне не следует использовать кортеж.
Я использую словари в качестве возвращаемого значения только тогда, когда сгруппированные объекты не всегда одинаковы. Подумайте необязательные заголовки электронной почты.
В остальных случаях, когда сгруппированные объекты имеют внутреннее значение внутри группы или нужен полноценный объект со своими собственными методами, я использую класс.
источник
Я предпочитаю:
Кажется, все остальное - просто дополнительный код для того же.
источник
источник
Как правило, «специализированная структура» фактически представляет собой разумное текущее состояние объекта со своими собственными методами.
Мне нравится искать имена для анонимных структур, где это возможно. Значимые имена делают вещи более понятными.
источник
Кортежи, диктовки и объекты Python предлагают программисту плавный компромисс между формальностью и удобством для небольших структур данных («вещей»). Для меня выбор того, как представлять вещь, продиктован главным образом тем, как я собираюсь использовать структуру. В C ++ общепринято использовать
struct
элементы только для данных иclass
объекты с методами, даже если вы можете легально помещать методы вstruct
; моя привычка похожа на Python, сdict
иtuple
вместоstruct
.Для наборов координат я буду использовать
tuple
вместо точкиclass
или adict
(и заметьте, что вы можете использовать atuple
в качестве словарного ключа, поэтому мыdict
создаем большие разреженные многомерные массивы).Если я собираюсь перебирать список вещей, я предпочитаю распаковывать
tuple
s на итерации:... так как версия объекта более загромождена, чтобы читать:
... не говоря уже о
dict
.Если эта вещь широко используется, и вы обнаруживаете, что выполняете аналогичные нетривиальные операции над ней в нескольких местах кода, то обычно стоит сделать ее объектом класса с соответствующими методами.
Наконец, если я собираюсь обмениваться данными с компонентами системы, отличными от Python, я чаще всего буду хранить их в
dict
том месте, которое лучше всего подходит для сериализации JSON.источник
+1 по предложению С. Лотта именованного контейнерного класса.
Для Python 2.6 и выше именованный кортеж предоставляет удобный способ простого создания этих контейнерных классов, а результаты «легковесны и требуют не больше памяти, чем обычные кортежи».
источник
В таких языках, как Python, я обычно использую словарь, так как он требует меньше затрат, чем создание нового класса.
Однако, если я постоянно возвращаю один и тот же набор переменных, то это, вероятно, связано с новым классом, который я выделю.
источник
Я бы использовал dict для передачи и возврата значений из функции:
Используйте переменную форму, как определено в форме .
Это самый эффективный способ для меня и для процессорного блока.
Вы должны передать только один указатель и вернуть только один указатель.
Вам не нужно изменять аргументы функций (их тысячи) всякий раз, когда вы вносите изменения в свой код.
источник
test
это непосредственно изменит значение. Сравните это с темdict.update
, что не возвращает значение.form
не ухудшает читаемость и производительность. В тех случаях, когда вам может потребоваться переформатироватьform
, возвращая его [форма] действительно гарантирует, что последнийform
будет возвращен, потому что вы нигде не будете отслеживать изменения формы.«Лучший» является частично субъективным решением. Используйте кортежи для небольших возвращаемых множеств в общем случае, когда неизменяемым является приемлемым. Кортеж всегда предпочтительнее списка, когда изменчивость не является обязательной.
Для более сложных возвращаемых значений или для случая, когда формальность важна (т. Е. Код высокого значения), лучше использовать именованный кортеж. Для самого сложного случая объект обычно лучший. Тем не менее, это действительно ситуация, которая имеет значение. Если имеет смысл возвращать объект, потому что это то, что у вас есть в конце функции (например, шаблон Factory), тогда верните объект.
Как сказал мудрец:
источник