Як повернути декілька значень з функції?

Канонічним способом повернення декількох значень у мовах, що його підтримують, часто є tpling.

Варіант: Використання кортежу

Розглянемо такий тривіальний приклад:

def f(x):
  y0 = x + 1
  y1 = x * 3
  y2 = y0 ** y3
  return (y0, y1, y2)

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

Варіант: Використання словника

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

Розглянемо наступне:

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 Reference Manual:

Оголошення __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 полягає у тому, що списки є змінюваними, тоді як кортежі - ні.

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

Питання

Після розлогої преамбули неминуче виникає питання. Який метод (на вашу думку) є кращим?

Я, як правило, використовую словниковий метод, оскільки він передбачає меншу підготовчу роботу. Однак, з точки зору типів, можливо, Вам краще піти шляхом класу, оскільки це може допомогти Вам уникнути плутанини в тому, що представляє собою словник.

З іншого боку, у спільноті Python є люди, які вважають, що неявним інтерфейсам слід надавати перевагу перед явними інтерфейсами, і в цьому випадку тип об'єкта не має значення, оскільки ви покладаєтесь на домовленість, що один і той самий атрибут завжди матиме однакове значення.

Отже, як ви повертаєте декілька значень у Python?

Я вважаю за краще:

def g(x):
  y0 = x + 1
  y1 = x * 3
  y2 = y0 ** y3
  return {'y0':y0, 'y1':y1 ,'y2':y2 }

Здається, що все інше - це просто додатковий код для того ж самого.

Коментарі (1)

Взагалі, &quo ;спеціалізована структура&quo ; фактично є осмисленим поточним станом об'єкта, зі своїми методами.

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

Я люблю знаходити назви для анонімних структур, де це можливо. Осмислені назви роблять речі більш зрозумілими.

Коментарі (0)

У таких мовах, як Python, я зазвичай використовую словник, оскільки це пов'язано з меншими накладними витратами, ніж створення нового класу.

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

Коментарі (0)