Вот мои мысли по этому поводу, и я попытаюсь объяснить, почему я считаю, что принятый ответ в основном неверен.
Прежде всего programming functions != mathematical functions
. Самое близкое, что вы можете получить к математическим функциям, - это если вы занимаетесь функциональным программированием, но даже тогда есть множество примеров, которые говорят об обратном.
- Функции не должны иметь ввод
- Функции не должны иметь вывод
- Функции не должны отображать входные данные в выходные (из-за двух предыдущих пунктов)
Функция с точки зрения программирования должна рассматриваться просто как блок памяти с началом (точкой входа функции), телом (пустым или иным образом) и точкой выхода (одна или несколько в зависимости от реализации). которые существуют для повторного использования кода, который вы написали. Даже если вы этого не видите, функция всегда что-то «возвращает». Это что-то на самом деле является адресом следующего оператора сразу после вызова функции. Это то, что вы увидите во всей своей красе, если вы будете выполнять какое-то действительно низкоуровневое программирование на языке ассемблера (я осмелюсь приложить дополнительные усилия и сделать машинный код вручную, как Линус Торвальдс, который очень часто упоминает об этом во время его семинары и интервью: D). Кроме того, вы также можете взять некоторые данные, а также выплевывать некоторые результаты. Вот почему
def foo():
pass
- совершенно правильный фрагмент кода.
Так почему возврат нескольких типов будет плохим? Ну ... Это совсем не так, если ты не злоупотребляешь этим. Это, конечно, вопрос плохих навыков программирования и / или незнания того, на каком языке вы говорите.
Разве не дешевле было бы вернуть None, чем создать новый пустой кортеж, или эта разница во времени слишком мала, чтобы заметить ее даже в более крупных проектах?
Насколько я знаю - да, возвращение объекта NoneType
было бы намного дешевле с точки зрения памяти. Вот небольшой эксперимент (возвращаемые значения в байтах):
>> sys.getsizeof(None)
16
>> sys.getsizeof(())
48
В зависимости от типа объекта, который вы используете в качестве возвращаемого значения (числовой тип, список, словарь, кортеж и т. Д.), Python управляет памятью различными способами, включая изначально зарезервированное хранилище.
Однако вы должны также учитывать код, который находится вокруг вызова функции, и как он обрабатывает все, что возвращает ваша функция. Вы проверяете на NoneType
? Или вы просто проверяете, имеет ли возвращаемый кортеж длину 0? Такое распространение возвращаемого значения и его типа (NoneType
в сравнении с пустым кортежем в вашем случае) на самом деле может быть более утомительным, чтобы справиться и взорвать вас в лицо. Не забывайте - сам код загружается в память, поэтому если для обработки NoneType
требуется слишком много кода (даже небольших кусков кода, но в большом количестве), лучше оставьте пустой кортеж, что также поможет избежать путаницы в умах люди, использующие вашу функцию и забывшие, что она на самом деле возвращает 2 типа значений.
Говоря о возврате нескольких типов значений, это та часть, в которой я согласен с принятым ответом (но только частично) - возвращение одного типа делает код более понятным, без сомнения. Гораздо проще проверить только для типа A, чем A, B, C и т. Д.
Однако Python является объектно-ориентированным языком и, как таковое, наследование, абстрактные классы и т. Д., И все, что является частью всего ООП-махинаций, вступает в игру. Это может пойти даже до генерации классов на лету, которые я обнаружил несколько месяцев назад и был ошеломлен (никогда не видел такого в C / C ++).
Примечание: Вы можете прочитать немного о метаклассах и динамических классах в этой хорошей обзорной статье с большим количеством примеров.
На самом деле существует множество шаблонов и техник проектирования, которых не было бы даже без так называемых полиморфных функций. Ниже я приведу две очень популярные темы (не могу найти лучшего способа суммировать обе за один термин):
- Утиная печать - часто часть языков динамической типизации, представителем которых является Python
- Фабричный шаблон разработки метода - в основном это функция, которая возвращает различные объекты на основе полученного ввода.
Наконец, возвращает ли ваша функция один или несколько типов, полностью зависит от проблемы, которую вы должны решить. Можно ли злоупотреблять этим полиморфным поведением? Конечно, как и все остальное.