Является ли функция pythonic возвращать несколько значений?

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

def divide(x, y):
    quotient = x/y
    remainder = x % y
    return quotient, remainder  

(q, r) = divide(22, 7)

Это кажется очень полезным, но похоже, что им также можно злоупотреблять («Ну… функция X уже вычисляет то, что нам нужно в качестве промежуточного значения. Пусть X также вернет это значение»).

Когда следует провести черту и определить другой метод?


person readonly    schedule 14.09.2008    source источник


Ответы (9)


Совершенно верно (для примера, который вы предоставили).

Кортежи - это первоклассные граждане в Python

Именно это и делает встроенная функция divmod().

q, r = divmod(x, y) # ((x - x%y)/y, x%y) Invariant: div*y + mod == x

Есть и другие примеры: zip, enumerate, dict.items.

for i, e in enumerate([1, 3, 3]):
    print "index=%d, element=%s" % (i, e)

# reverse keys and values in a dictionary
d = dict((v, k) for k, v in adict.items()) # or 
d = dict(zip(adict.values(), adict.keys()))

Кстати, скобки в большинстве случаев не нужны. Цитата из справочника по библиотеке Python:

Кортежи могут быть созданы несколькими способами:

  • Использование пары круглых скобок для обозначения пустого кортежа: ()
  • Использование конечной запятой для одноэлементного кортежа: a или (a,)
  • Разделение элементов запятыми: a, b, c или (a, b, c)
  • Использование встроенного tuple (): tuple () или tuple (итерация)

Функции должны служить единственной цели

Следовательно, они должны возвращать один объект. В вашем случае этот объект является кортежем. Рассматривайте кортеж как специальную составную структуру данных. Есть языки, в которых почти каждая функция возвращает несколько значений (список в Лиспе).

Иногда достаточно вернуть (x, y) вместо Point(x, y).

Именованные кортежи

С введением именованных кортежей в Python 2.6 во многих случаях предпочтительнее возвращать именованные кортежи вместо простых кортежей.

>>> import collections
>>> Point = collections.namedtuple('Point', 'x y')
>>> x, y = Point(0, 1)
>>> p = Point(x, y)
>>> x, y, p
(0, 1, Point(x=0, y=1))
>>> p.x, p.y, p[0], p[1]
(0, 1, 0, 1)
>>> for i in p:
...   print(i)
...
0
1
person jfs    schedule 14.09.2008
comment
Возможность использовать такие кортежи очень удобна. Иногда мне очень не хватает этой способности в Java. - person MBCook; 25.02.2009
comment
Большое спасибо за упоминание именованных кортежей. Я давно искал что-то подобное. - person vobject; 10.09.2009
comment
для ограниченных возвращаемых значений dict () может быть проще / быстрее (особенно, если функция затем используется для REST API и JSON). в моей системе def test_nt (): Point = namedtuple ('Point', ['x', 'y']) p = Point (10.5, 11.5) json.dumps (p._asdict ()) занимает 819 мкс / цикл , def test_dict (): d = {'x': 10.5, 'y': 11.5} json.dumps (d) занимает 15,9 мкс / цикл .... так что ›в 50 раз быстрее - person comte; 05.06.2017
comment
@comte: Да, Point(10.5, 11.5) в 6 раз медленнее, чем {'x': 10.5, 'y':11.5}. Абсолютное время: 635 ns +- 26 ns vs. 105 ns +- 4 ns. Маловероятно, что это будет узким местом в приложении - не оптимизируйте, если ваш профилировщик не говорит иначе. Не создавайте классы на уровне функций, если вы не знаете, зачем вам это нужно. Если вашему API требуется dict, а не dict - это не имеет ничего общего с производительностью. - person jfs; 16.06.2017

Во-первых, обратите внимание, что Python допускает следующее (в скобках нет необходимости):

q, r = divide(22, 7)

Что касается вашего вопроса, в любом случае нет жесткого правила. Для простых (и обычно надуманных) примеров может показаться, что данная функция всегда может иметь единственную цель, приводящую к единственному значению. Однако при использовании Python для реальных приложений вы быстро сталкиваетесь со многими случаями, когда необходимо возвращать несколько значений, что приводит к более чистому коду.

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

person Jason Etheridge    schedule 14.09.2008
comment
запятая создает кортеж, поэтому выражение: q, r является кортежем. - person jfs; 15.09.2008
comment
Винко, примером является tempfile.mkstemp () из стандартной библиотеки, которая возвращает кортеж, содержащий дескриптор файла и абсолютный путь. JFS, да, ты прав. - person Jason Etheridge; 15.09.2008
comment
Но это одна цель ... У вас может быть одна цель и несколько возвращаемых значений - person Vinko Vrsalovic; 15.09.2008
comment
Если бы другие языки могли иметь несколько типов возврата, я бы не зацикливался на ссылках и выходных параметрах. - person orip; 05.12.2008
comment
Возврат нескольких значений - одна из величайших функций Python! - person Martin Beckett; 25.02.2009

Приведенный вами пример на самом деле является встроенной функцией python с именем divmod. Итак, кто-то в какой-то момент подумал, что он достаточно питонический, чтобы включить его в базовую функциональность.

Для меня, если это делает код чище, он питонический. Сравните эти два блока кода:

seconds = 1234
minutes, seconds = divmod(seconds, 60)
hours, minutes = divmod(minutes, 60)

seconds = 1234
minutes = seconds / 60
seconds = seconds % 60
hours = minutes / 60
minutes = minutes % 60
person Nathan Jones    schedule 14.09.2008

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

  1. Иногда очень и очень полезно возвращать несколько значений. Возьмем, к примеру, метод, который необязательно обрабатывает событие (при этом возвращает какое-то значение), а также возвращает успех или неудачу. Это может возникнуть в цепочке ответственности. В других случаях вы хотите вернуть несколько тесно связанных частей данных - как в приведенном примере. В этом случае возврат нескольких значений сродни возврату одного экземпляра анонимного класса с несколькими переменными-членами.
  2. Обработка аргументов метода в Python требует возможности напрямую возвращать несколько значений. В C ++, например, аргументы метода могут передаваться по ссылке, поэтому вы можете назначать им выходные значения в дополнение к формальному возвращаемому значению. В Python аргументы передаются «по ссылке» (но в смысле Java, а не C ++). Вы не можете присвоить новые значения аргументам метода и отразить их за пределами области действия метода. Например:

    // C++
    void test(int& arg)
    {
        arg = 1;
    }
    
    int foo = 0;
    test(foo); // foo is now 1!
    

    Сравнить с:

    # Python
    def test(arg):
        arg = 1
    
    foo = 0
    test(foo) # foo is still 0
    
person zweiterlinde    schedule 15.09.2008
comment
метод, который опционально обрабатывает событие, а также возвращает успех или неудачу - вот для чего нужны исключения. - person Nathan; 19.10.2010
comment
Очень широко признано, что исключения предназначены только для исключительных условий, а не просто для успеха или неудачи. Коды ошибок Google и исключения для некоторых обсуждений. - person zweiterlinde; 06.11.2010

Это определенно питонический. Тот факт, что вы можете возвращать несколько значений из функции, которая является шаблоном, который у вас был бы на таком языке, как C, где вам нужно определить структуру для каждой комбинации типов, которые вы где-то возвращаете.

Однако, если вы достигли точки, когда вы возвращаете что-то сумасшедшее, например 10 значений из одной функции, вам следует серьезно подумать о том, чтобы объединить их в класс, потому что в этот момент это становится громоздким.

person indentation    schedule 15.09.2008

Возвращение кортежа - это круто. Также обратите внимание на новый namedtuple, который был добавлен в python 2.6, который может сделать его более удобным для вас: http://docs.python.org/dev/library/collections.html#collections.namedtuple

person pixelbeat    schedule 15.09.2008

ОТ: В языке Algol68 RSRE есть любопытный оператор "/: =". например.

INT quotient:=355, remainder;
remainder := (quotient /:= 113);

Даем частное 3 и остаток 16.

Примечание: обычно значение «(x /: = y)» отбрасывается, поскольку частное «x» назначается по ссылке, но в случае RSRE возвращаемое значение - это остаток.

c.f. Целочисленная арифметика - Algol68

person NevilleDNZ    schedule 12.03.2009

Можно возвращать несколько значений, используя кортеж для простых функций, таких как divmod. Если это делает код читабельным, то это Pythonic.

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

person Will Harris    schedule 15.09.2008

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

def divide(x, y):
    return {'quotient': x/y, 'remainder':x%y }

answer = divide(22, 7)
print answer['quotient']
print answer['remainder']
person Fred Larson    schedule 15.09.2008
comment
Не делай этого. В однострочном режиме результат не назначается, это очень неуклюже. (Если вы не хотите сохранить результат, и в этом случае лучше поместить его в метод.) Если ваше намерение с dict состоит в том, чтобы самостоятельно задокументировать возвращаемые значения, тогда а) дайте fn разумно понятное имя (например, divmod ) и б) поместите остальную часть объяснения в строку документации (которая является питоническим местом для их размещения); Если для этой строки документации требуется более двух строк, это говорит о том, что функция тематически перегружена, и это может быть плохой идеей. - person smci; 24.05.2011