утверждать foo().bar в foobar().baz

Лично я считаю, что синтаксис словаря для Python — наименее удачная вещь в Python. Все эти name[‘in’][‘quotes’][‘and’][‘brakets’] очень тяжело читать. Гораздо лучше иметь names.with.a.dot.notation, не так ли?

К сожалению, точечная нотация зарезервирована для модулей и классов из-за их модульности и классики. Словарь просто недостаточно «классный» для точечной записи. облом.

Если вы хотите реализовать полноценный класс в качестве возвращаемого значения, вы обнаружите, что вам нужно поддерживать все эти магические методы сравнения, вызова, копирования, хеширования и т. д. Это много шаблонного кода. Есть классы, облегчающие вашу жизнь (nameddict и т. д.), но все они слишком хлопотны для простого случая.

Случай, о котором я говорю, намного проще: просто сделайте return более аккуратным и простым в использовании.

Проблема

Нам нужно вернуть несколько значений из функции.

Наивные решения

def foo():
  return 1, 2, 'foo'

Ключевая проблема заключается в том, что возвращаемые значения имеют нулевое значение. Для их использования необходимо распаковать значения в переменные или получить к ним доступ по индексу:

assert foo()[2] == 'foo'

Индексирование с нуля добавляет веселья к подсчету. Пароль находится в элементе номер 5 или в элементе номер 3?

Другой подход - вернуть словарь:

def foo():
  return {
     'odd': 1,
     'even': 2,
     'string_example': 'foo'
  }

Теперь у нас есть имена для возвращаемых значений. Взамен вызывающему нужно пострадать:

assert foo()['string_example'] == 'foo'

Хорошее решение

Мое решение - использовать класс в качестве держателя данных. Мы проигнорируем все причудливые вещи, которые мы можем сделать, и постараемся максимально упростить код:

def foo():
  class retval:
    odd = 1
    even = 2
    string_example = 'foo'
  return retval

Использование - это счастье:

assert foo().string_example == 'foo'

Но становится еще лучше, если вы рассматриваете типы контейнеров для возвращаемых значений:

def bar():
  class retval:
     somelist = []
     adict = {}
  retval.somelist.append(2)
  retval.somelist.append(4)
  retval.adict[3] = 4
  retval.adict[’baz’] = 'something else'
  return retval

В этом примере мы не боимся использовать переменные класса с контейнерными типами, поскольку класс «retval» создается каждый раз при выполнении функции, поэтому здесь нет (не)ожидаемых сюрпризов.

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

assert bar().adict['baz'] == 'something else'

Причина, по которой я обычно не хочу использовать полную точечную нотацию, заключается в том, что эти возвращаемые значения могут быть переданы другой функции, и эта функция будет удивлена ​​нестандартным объектом (или вам нужно написать много кода, чтобы не чтобы удивить).

assert 3 in bar().adict.keys()

Если ваш adict не словарь, то это либо сюрприз, либо много кода. Но один уровень «классности» для нескольких возвращаемых значений экономит много времени, сохраняя при этом стандартные типы.