Zen of Python утверждает, что должен быть только один способ сделать что-то, но часто я сталкиваюсь с проблемой принятия решения о том, когда использовать функцию или когда использовать метод.
Давайте рассмотрим тривиальный пример - объект ChessBoard. Пусть говорят, что нам нужен какой-то способ получить все законные корольские движения, доступные на доске. Мы пишем ChessBoard.get_king_moves() или get_king_moves (chess_board)?
Вот некоторые связанные вопросы, на которые я смотрел:
- Почему python использует "магические методы" ?
- Есть ли причина, по которой строки Python не имеют метода длины строки?
Ответы, которые я получил, были в основном неубедительными:
Почему Python использует методы для некоторых функций (например, list.index()), но работает для других (например, len (list))?
Основная причина - история. Функции были использованы для тех операций, которые были типичными для группы типов, и которые были предназначен для работы даже для объектов, которые вообще не имеют методов (например, кортежи). Также удобно иметь функцию, которая может легко применять к аморфной коллекции объектов, когда вы используете функциональные возможности Python (map(), apply() и др.).
Фактически, реализация len(), max(), min() в качестве встроенной функции на самом деле меньше кода, чем реализация их как методов для каждого типа. Можно окунуться в отдельные случаи, но это часть Python и его слишком поздно сделать такие фундаментальные изменения. Эти функции чтобы избежать значительного повреждения кода.
Несмотря на то, что вышеизложенное не говорит особо о том, какую стратегию принять.
Это одна из причин - с помощью пользовательских методов разработчики бесплатно выбрать другое имя метода, например getLength(), length(), getlength() или вообще. Python обеспечивает строгое именование так, что можно использовать общую функцию len().
Чуть более интересный. Я считаю, что функции в некотором смысле являются версией интерфейсов Pythonic.
Наконец, из самого Guido:
Говоря о способностях/интерфейсах, я подумал о некоторых из наших Названия специальных методов "изгоев". В Справочнике по языку говорится: "A класс может реализовать определенные операции, которые вызываются специальными синтаксис (например, арифметические операции или субтипирование и нарезка) на определяя методы со специальными именами". Но есть все эти методы со специальными именами, такими как
__len__
или__unicode__
, которые кажутся предоставляемых в интересах встроенных функций, а не для поддержка синтаксиса. Предположительно, в Python на основе интерфейса эти методы превратились бы в регулярно названные методы на ABC, так что__len__
станетclass container: ... def len(self): raise NotImplemented
Хотя, подумав об этом еще, я не понимаю, почему все синтаксические операции не будут просто вызывать соответствующий нормальный метод на конкретной ABC. Например, "
<
", предположительно, вызывает "object.lessthan
" (или, возможно, "comparable.lessthan
" ). Так что еще один выгодой будет способность отучить Питона от этого странное имя, которое кажется мне улучшением HCI.Hm. Я не уверен, что согласен (рисунок: -).
Есть два бита "обоснования Питона", которые я хотел бы объяснить первый.
Прежде всего, я выбрал len (x) по x.len() по причинам HCI (
def __len__()
пришел намного позже). На самом деле есть две взаимосвязанные причины: и HCI:(a) Для некоторых операций префиксная нотация читается лучше, чем Операции postfix - prefix (и infix!) имеют давнюю традицию в математика, которая любит записи, в которых математик думает о проблеме. Сравните легкость, с которой мы переписать формулу типа
x*(a+b)
вx*a + x*b
на неуклюжесть делая то же самое, используя необработанную нотацию OO.(b) Когда я читаю код, который говорит
len(x)
, я знаю, что он просит длина чего-то. Это говорит мне о двух вещах: результат integer, а аргумент - это какой-то контейнер. Иначе, когда я читаюx.len()
, я должен уже знать, чтоx
- это своего рода контейнер, реализующий интерфейс или наследующий от класса, который имеет стандартlen()
. Свидетельствуйте путаницу, которую мы иногда имеем, когда класс, не реализующий отображение, имеетget()
илиkeys()
метод или что-то, что не является файлом, имеет методwrite()
.Говоря то же самое по-другому, я вижу "len" как встроенный операция. Мне бы очень хотелось потерять это. Я не могу точно сказать, означает ли вы это или нет, но "def len (self):...", похоже, похоже на вас хочу понизить его до обычного метода. Я сильно -1 на этом.
Второй бит обоснования Python, который я обещал объяснить, является причиной почему я выбрал специальные методы для поиска
__special__
, а не простоspecial
. Я ожидал много операций, которые могут потребоваться классам для переопределения, некоторые стандартные (например,__add__
или__getitem__
), некоторые не такие стандартный (например, рассол__reduce__
в течение длительного времени не имел поддержки в C код вообще). Я не хотел, чтобы эти специальные операции использовали обычные имена методов, потому что тогда уже существовавшие классы или классы, написанные пользователи без энциклопедической памяти для всех специальных методов, будут подвержены случайному определению операций, которые они не с возможными катастрофическими последствиями. Иван Крстич объяснил это более кратким в своем послании, которое прибыло после того, как я все это написано.- --Guido van Rossum (домашняя страница: http://www.python.org/~guido/)
Мое понимание этого заключается в том, что в некоторых случаях префиксная нотация имеет смысл больше (т.е. Duck.quack имеет больше смысла, чем quack (Duck) с лингвистической точки зрения.) И снова функции допускают "интерфейсы".
В таком случае я предполагал бы реализовать get_king_moves, основываясь исключительно на первой точке Guido. Но это все еще оставляет много открытых вопросов, касающихся, скажем, реализации класса стека и очереди с похожими методами push и pop - должны ли они быть функциями или методами? (здесь я бы угадал функции, потому что я действительно хочу сигнализировать push-поп-интерфейс)
TL;DR: Может кто-нибудь объяснить, какая стратегия для решения, когда использовать функции против методов, должна быть?