Каноническим способом возврата нескольких значений на языках, которые его поддерживают, часто является кортеж.
Рассмотрим этот тривиальный пример:
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:
The
__slots__declaration takes a sequence of instance variables and reserves just enough space in each instance to hold a value for each variable. Space is saved because__dict__is not created for each instance.
Используя новые классы данных 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 состоит в том, что списки - это изменчивый, а кортежи - нет.
Лично я стараюсь перенести условности функционального программирования: использовать списки для любого количества элементов одного типа и кортежи для фиксированного количества элементов заранее определенных типов.
После пространной преамбулы возникает неизбежный вопрос. Как вы думаете, какой метод лучше?
Многие хорошие вопросы с отличными ответами закрываются, потому что возникает ключевое слово «мнение». Вы можете утверждать, что вся SO основана на мнении, но это мнение, основанное на фактах, ссылках и конкретном опыте. Тот факт, что кто-то спрашивает, «что вы думаете лучше всего», не означает, что они спрашивают личное мнение, абстрагируемое от реальных фактов, ссылок и конкретного опыта. Они почти наверняка запрашивают именно такое мнение, основанное на фактах, ссылках и конкретном опыте, которые человек использовал при формировании мнения, и документально подтвержденный ими.
@hetepeperfan не нужно менять 3, и ни один из них не определяет y3 в глобальном масштабе, вы также можете использовать локальное имя y3, которое также будет выполнять ту же работу.






Я предпочитаю:
def g(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return {'y0':y0, 'y1':y1 ,'y2':y2 }
Кажется, все остальное - это просто дополнительный код, делающий то же самое.
Кортежи легче распаковать: y0, y1, y2 = g () с диктовкой, которую вы должны сделать: result = g () y0, y1, y2 = result.get ('y0'), result.get ('y1' ), result.get ('y2'), что немного некрасиво. У каждого решения есть свои «плюсы» и «минусы».
В таких языках, как Python, я обычно использую словарь, поскольку он требует меньше накладных расходов, чем создание нового класса.
Однако, если я обнаруживаю, что постоянно возвращаю один и тот же набор переменных, это, вероятно, связано с новым классом, который я исключу.
Как правило, «специализированная структура» на самом деле ЯВЛЯЕТСЯ разумным текущим состоянием объекта со своими собственными методами.
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
Мне нравится находить имена для анонимных структур там, где это возможно. Значимые имена проясняют ситуацию.
Для небольших проектов проще всего работать с кортежами. Когда это становится слишком сложно управлять (и не раньше), я начинаю группировать вещи в логические структуры, однако я считаю, что предложенное вами использование словарей и объектов 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).
Вопрос - есть ли разница между size, type, dimensions = getImageData(x) и (size, type, dimensions) = getImageData(x)? То есть, имеет ли значение перенос левой части кортежного присваивания?
@ Reb.Cabin Нет разницы. Кортеж обозначается запятой, а круглые скобки используются только для группировки элементов. Например, (1) - это int, а (1,) или 1, - это кортеж.
Re "возврат словаря с ключами y0, y1, y2 и т. д. Не дает никаких преимуществ перед кортежами": словарь имеет преимущество в том, что вы можете добавлять поля в возвращаемый словарь, не нарушая существующий код.
Повторно «возврат словаря с ключами y0, y1, y2 и т. д. Не дает никаких преимуществ по сравнению с кортежами»: он также более читабелен и менее подвержен ошибкам, поскольку вы получаете доступ к данным на основе его имени, а не положения.
Я предпочитаю использовать кортежи всякий раз, когда кортеж кажется «естественным»; координаты являются типичным примером, когда отдельные объекты могут стоять сами по себе, например в одноосных вычислениях только масштабирование, и порядок важен. Примечание: если я могу отсортировать или перемешать элементы без отрицательного влияния на значение группы, то мне, вероятно, не следует использовать кортеж.
Я использую словари в качестве возвращаемого значения только тогда, когда сгруппированные объекты не всегда одинаковы. Подумайте о необязательных заголовках электронной почты.
Для остальных случаев, когда сгруппированные объекты имеют внутреннее значение внутри группы или нужен полноценный объект со своими собственными методами, я использую класс.
Голосую за словарь.
Я обнаружил, что если я создам функцию, которая возвращает более 2-3 переменных, я свожу их в словарь. В противном случае я забываю порядок и содержание того, что возвращаю.
Кроме того, введение «специальной» структуры затрудняет отслеживание вашего кода. (Кому-то еще придется поискать код, чтобы узнать, что это такое)
Если вас беспокоит поиск типа, используйте описательные ключи словаря, например, «список значений x».
def g(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return {'y0':y0, 'y1':y1 ,'y2':y2 }
после многих лет программирования я стремлюсь к любой структуре данных и функциям, которые требуются. Сначала функция, вы всегда можете выполнить рефакторинг по мере необходимости.
Как получить значения в словаре, не вызывая функцию несколько раз? Например, если я хочу использовать y1 и y3 в другой функции?
присвоить результаты отдельной переменной. result = g(x); other_function(result)
@monkut да. Этот способ также позволяет передавать результат нескольким функциям, которые принимают разные аргументы из результата, без необходимости каждый раз специально ссылаться на определенные части результата.
+1 к предложению С.Лотта именованного контейнерного класса.
Для Python 2.6 и выше названный кортеж предоставляет удобный способ простого создания этих контейнерных классов, а результаты «легковесны и требуют не больше памяти, чем обычные кортежи».
Для этого в 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
Это единственно правильный ответ, потому что это единственная каноническая структура, которую OP не рассматривал, и потому, что она решает его проблему управления длинными кортежами. Должен быть отмечен как принятый.
Что ж, логическим обоснованием разработки namedtuple является меньший объем памяти для результатов масса (длинные списки кортежей, такие как результаты запросов к БД). Для отдельных элементов (если рассматриваемая функция не часто вызывается) словари и классы тоже подойдут. Но именованные кортежи и в этом случае являются хорошим решением.
Думаю, лучший ответ. Я не сразу понял одну вещь - вам не нужно объявлять namedtuple во внешней области видимости; ваша функция сама может определять контейнер и возвращать его.
@wom: Не делай этого. Python не пытается унифицировать определения namedtuple (каждый вызов создает новый), создание класса namedtuple относительно дорого как с точки зрения ЦП, так и с точки зрения памяти, а все определения классов по своей сути включают циклические ссылки (поэтому на CPython вы ждете циклического GC run для их выпуска). Это также делает невозможным использование класса pickle (и, следовательно, в большинстве случаев невозможным использование экземпляров с multiprocessing). Каждое создание класса в моем 3.6.4 x64 потребляет ~ 0,337 мс и занимает чуть менее 1 КБ памяти, что сводит на нет любую экономию экземпляра.
Замечу, Python 3.7 улучшена скорость создания новых классов namedtuple; затраты на процессор снижаются примерно в 4 раза, но они по-прежнему примерно в 1000 раз выше, чем стоимость создания экземпляра, а стоимость памяти для каждого класса остается высокой (я ошибался в своем последнем комментарии о «менее 1 КБ» для класса, _source сам по себе обычно 1,5 КБ; _source удален в версии 3.7, поэтому он, вероятно, ближе к первоначальному заявлению - чуть менее 1 КБ на создание класса).
Как жаль, что этого нет в стандартной библиотеке. Нужен import collections. Поэтому продолжаю пользоваться словарями.
@SergeStroobandt - это часть стандартной библиотеки, но не встроенная. Вам не нужно беспокоиться о том, что он может быть не установлен в другой системе с Python> = 2.6. Или вы просто возражаете против лишней строчки кода?
Разве сейчас не будет лучшим вариантом types.SimpleNamespace?
@jaaq Почему так лучше?
@endolith, потому что вы можете добавлять значения после его создания, то есть вы можете добавлять результаты в пространство имен retval, как только вы их получите, и вам не нужно ждать, пока вы сможете поместить их в именованный кортеж сразу. Иногда может значительно уменьшить беспорядок в более крупных функциях.
@jaaq: Проблема в том, что types.SimpleNamespace - это прославленный dict. Это означает значительно более высокую стоимость памяти на экземпляр, повышенную стоимость создания, отсутствие возможности распаковки, как у tuple, и т. д.
Кортежи, диктовки и объекты 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.
Другой вариант - использовать генераторы:
>>> 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 обычно лучше, за исключением случаев, когда возвращаемые значения являются кандидатами для инкапсуляции в класс.
Это кажется наиболее чистым решением с чистым синтаксисом. Есть ли у этого недостатки? Если вы не используете все доходы, есть ли «неизрасходованные» доходы, которые могут вам навредить?
Это может показаться «чистым», но совсем не интуитивно понятным. Как может кто-то, кто никогда раньше не сталкивался с этим шаблоном, знал, что автоматическая распаковка кортежа будет запускать каждый yield?
@CoreDumpError, генераторы - это всего лишь ... генераторы. Нет никакой внешней разницы между def f(x): …; yield b; yield a; yield r и (g for g in [b, a, r]), и оба легко конвертируются в списки или кортежи и, как таковые, будут поддерживать распаковку кортежей. Форма генератора кортежей следует функциональному подходу, в то время как форма функции является обязательной и позволяет управлять потоком и назначать переменные.
>>> def func():
... return [1,2,3]
...
>>> a,b,c = func()
>>> a
1
>>> b
2
>>> c
3
@edouard Нет, он возвращает кортеж, а не список.
деструктуризация - это аргумент то для возврата списков на мой взгляд
Многие ответы предполагают, что вам нужно вернуть какую-то коллекцию, например словарь или список. Вы можете отказаться от лишнего синтаксиса и просто записать возвращаемые значения через запятую. Примечание: технически это возвращает кортеж.
def f():
return True, False
x, y = f()
print(x)
print(y)
дает:
True
False
Вы все еще возвращаете коллекцию. Это кортеж. Я предпочитаю скобки, чтобы сделать его более ясным. Попробуйте следующее: type(f()) возвращает <class 'tuple'>.
@Igor: Нет причин делать явный аспект tuple; На самом деле не важно, что вы возвращаете tuple, это идиома для возврата периода с несколькими значениями. По той же причине вы опускаете скобки с идиомой подкачки, x, y = y, x, множественная инициализация x, y = 0, 1 и т. Д .; Конечно, он делает tuple под капотом, но нет причин делать это явным, так как tuple совсем не главное. Учебник Python вводит множественное назначение задолго до того, как он коснется tuple.
@ShadowRanger любая последовательность значений, разделенных запятой в правой части =, является кортежем в Python с круглыми скобками или без них. Так что на самом деле здесь нет явного или неявного. a, b, c - такой же кортеж, как (a, b, c). Кроме того, при возврате таких значений кортеж «под капотом» не создается, потому что это просто простой кортеж. OP уже упоминал кортежи, поэтому на самом деле нет разницы между тем, что он упомянул, и тем, что показывает этот ответ. Никто
@ Ken4scholars: Да, определение языка рассматривает их как эквивалентные. Тем не менее, настоящие программисты склонны рассматривать скобки как указание на структуру данных последовательности, в то время как без них это просто «несколько вещей». Я согласен, что нет никакой разницы в поведении, но лишние скобки меняют то, как большинство людей, которых я знаю, думают об этом. Согласен, это не отличается от примера OP по поведению (со всеми теми же ограничениями, которые OP не любил).
Это буквально первый вариант, предложенный в вопросе
@endolith Два раза, когда парень задает вопрос («Как мне вернуть несколько значений?» и «Как ты возвращает несколько значений?») отвечает этим ответом. Иногда менялся текст вопроса. И это вопрос, основанный на мнении.
Я бы использовал 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}
Для меня и для процессора это наиболее эффективный способ.
Вам нужно передать только один указатель и вернуть только один указатель.
Вам не нужно изменять аргументы функций (тысячи из них) всякий раз, когда вы вносите изменения в свой код.
Dicts изменчивы. Если вы передаете dict функции, и эта функция редактирует dict, изменения будут отражены за пределами этой функции. Если функция возвращает dict в конце, это может означать, что функция не имеет побочных эффектов, поэтому значение не должно возвращаться, что дает понять, что test напрямую изменит значение. Сравните это с dict.update, который не возвращает значения.
@sleblanc «Если функция возвращает словарный запас в конце, это может означать, что функция не имеет побочных эффектов». Это не означает, что, как вы сказали, dict изменяем. Однако возвращение form не ухудшает читаемость или производительность. В случаях, когда вам может потребоваться переформатировать form, возвращение его [form] гарантирует, что будет возвращен последний form, потому что вы не будете нигде отслеживать изменения формы.
«Лучшее» - решение отчасти субъективное. Используйте кортежи для небольших возвращаемых наборов в общем случае, когда допустимо неизменяемое значение. Кортеж всегда предпочтительнее списка, если изменчивость не является обязательной.
Для более сложных возвращаемых значений или для случая, когда важна формальность (например, код высокого значения), лучше использовать именованный кортеж. В самом сложном случае лучше всего подходит объект. Однако действительно важна ситуация. Если имеет смысл возвращать объект, потому что это то, что у вас, естественно, есть в конце функции (например, шаблон Factory), тогда верните объект.
Как сказал мудрец:
Premature optimization is the root of all evil (or at least most of it) in programming.
В ваших отличных примерах вы используете переменную
y3, но если y3 не объявлен глобальным, это приведет кNameError: global name 'y3' is not defined, возможно, просто используйте3?