Чи є пітонічним для функції повернення кількох значень?


81

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

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

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

Це здається дуже корисним, але, схоже, ним також можна зловживати ("Ну..функція X вже обчислює те, що нам потрібно як проміжне значення. Давайте X також поверне це значення").

Коли слід проводити лінію та визначати інший метод?

Відповіді:


107

Абсолютно (для прикладу, який ви надали).

Кортежі - це першокласні громадяни Пітона

Існує вбудована функція, 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)
  • Використання вбудованого кортежу (): кортеж () або кортеж (ітерабельний)

Функції повинні мати єдину мету

Тому вони повинні повернути один об'єкт. У вашому випадку цей об'єкт - кортеж. Розглянемо кортеж як спеціальну складну структуру даних. Є мови, де майже кожна окрема функція повертає кілька значень (список у Lisp).

Іноді достатньо повернутися (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

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 мкс / цикл , def test_dict (): d = {'x': 10.5, 'y': 11.5} json.dumps (d) займає 15,9 мкс / цикл .... так> швидше в 50 разів
Comte

@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, так, ти маєш рацію.
Джейсон Етрідж

Але це Єдина мета ... Ви можете мати одну ціль і кілька повернутих значень
Вінко Врсалович

Якби інші мови могли мати кілька типів повернення, я б не застряг у посиланнях та параметрах "out".
орип

Повернення декількох значень - одна з найбільших можливостей у 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
    

"метод, який необов'язково обробляє подію, а також повертає успіх або невдачу" - для цього призначені винятки.
Натан,

Широко прийнято, що винятки стосуються лише "виняткових" умов, а не простого успіху чи невдачі. Google "коди помилок проти винятків" для деяких дискусій.
zweiterlinde

1

Це, безумовно, пітонічно. Справа в тому, що ви можете повернути кілька значень із функції шаблону, яку ви мали б на такій мові, як C, де вам потрібно визначити структуру для кожної комбінації типів, які ви десь повертаєте.

Однак, якщо ви дійдете до точки, коли ви повернете щось божевільне, як 10 значень з однієї функції, вам слід серйозно подумати про їх об'єднання в клас, оскільки в цей момент це стає громіздким.



1

OT: Algol68 RSRE має цікавий оператор "/: =". напр.

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

Даючи частку 3, а решту 16.

Примітка: зазвичай значення "(x /: = y)" відкидається, оскільки частка "x" призначається за посиланням, але у випадку RSRE повернене значення є залишком.

cf Ціла арифметика - Algol68


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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.