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

81

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

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

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

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

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

Только чтение
источник

Ответы:

107

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

Кортежи - это первоклассные граждане в 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
jfs
источник
1
Возможность использовать такие кортежи очень удобна. Иногда мне очень не хватает этой способности в Java.
MBCook,
3
Большое спасибо за упоминание именованных кортежей. Я давно искал что-то подобное.
vobject
для ограниченных возвращаемых значений dict () может быть проще / быстрее (особенно если функция затем используется для REST API и JSON). в моей системе def test_nt (): Point = namedtuple ('Point', ['x', 'y']) p = Point (10.5, 11.5) json.dumps (p._asdict ()) занимает 819 мкс / цикл , Защита test_dict (): d = { 'х': 10,5, 'у': 11.5} json.dumps (г) принимает 15.9μs / цикл .... так> 50x быстрее
Конт
@comte: Да, Point(10.5, 11.5)в 6 раз медленнее, чем {'x': 10.5, 'y':11.5}. Абсолютные времена 635 ns +- 26 nsпротив105 ns +- 4 ns . Маловероятно, что это будет узким местом в приложении - не оптимизируйте, если ваш профилировщик не говорит иначе. Не создавайте классы на уровне функций, если вы не знаете, зачем вам это нужно. Если ваш API требует dictчем использовать dict- это не имеет никакого отношения к производительности.
jfs
27

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

q, r = divide(22, 7)

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

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

Джейсон Этеридж
источник
4
запятая создает кортеж, поэтому выражение: q, r является кортежем.
jfs
Винко, примером является tempfile.mkstemp () из стандартной библиотеки, которая возвращает кортеж, содержащий дескриптор файла и абсолютный путь. JFS, да, ты прав.
Джейсон Этеридж,
Но это ЕСТЬ единственная цель ... У вас может быть одна цель и несколько возвращаемых значений
Винко Врсалович
Если бы другие языки могли иметь несколько типов возврата, я бы не зацикливался на ссылках и выходных параметрах.
Орип
Возврат нескольких значений - одна из величайших функций Python!
Мартин Беккет
13

Приведенный вами пример на самом деле является встроенной функцией 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
Натан Джонс
источник
3

Да, возврат нескольких значений (т. Е. Кортежа) определенно питонический. Как отмечали другие, есть много примеров в стандартной библиотеке 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
    
Zweiterlinde
источник
«метод, который опционально обрабатывает событие, а также возвращает успех или неудачу» - вот для чего нужны исключения.
Натан,
Очень широко признано, что исключения предназначены только для «исключительных» условий, а не просто для успеха или неудачи. Google "коды ошибок и исключения" для некоторых обсуждений.
zweiterlinde 06
1

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

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

отступ
источник
1

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

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

Получаем частное от 3 и остаток от 16.

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

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

NevilleDNZ
источник
0

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

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

Уилл Харрис
источник
0

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

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

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