Дзен 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.
Наконец, от самого Гвидо :
Разговор о способностях / интерфейсах заставил меня задуматься о некоторых из наших "мошеннических" имен специальных методов. В «Справочнике по языку» говорится: «Класс может реализовывать определенные операции, которые вызываются специальным синтаксисом (например, арифметические операции или индексирование и разрезание), определяя методы со специальными именами». Но есть все эти методы со специальными именами вроде
__len__
или,__unicode__
которые, кажется, предоставляются для использования встроенных функций, а не для поддержки синтаксиса. Предположительно в Python, основанном на интерфейсе, эти методы превратятся в методы с регулярными именами на ABC, так что__len__
они станутclass container: ... def len(self): raise NotImplemented
Хотя, если подумать об этом еще раз, я не понимаю, почему все синтаксические операции просто не вызывают соответствующий метод с обычным именем для конкретной ABC. "
<
", например, предположительно вызовет "object.lessthan
" (или, возможно, "comparable.lessthan
"). Таким образом, еще одним преимуществом будет возможность отучить Python от этой странности искаженного имени, что, как мне кажется, является улучшением HCI .Гектометр Я не уверен, что согласен (подумайте, что :-).
Есть две части «обоснования Python», которые я хотел бы объяснить в первую очередь.
Во-первых, я выбрал len (x) вместо x.len () по причинам HCI (появившимся
def __len__()
намного позже). На самом деле есть две взаимосвязанные причины, обе - HCI:(a) Для некоторых операций префиксная нотация просто читается лучше, чем постфиксная - префиксные (и инфиксные!) операции имеют давнюю традицию в математике, которой нравятся нотации, в которых визуальные элементы помогают математику размышлять над проблемой. Сравните легкость, с которой мы переписываем формулу как
x*(a+b)
в,x*a + x*b
с неуклюжестью делать то же самое, используя необработанную объектно-ориентированную нотацию.(б) Когда я читаю код, который говорит, что
len(x)
я знаю, что он запрашивает длину чего-то. Это говорит мне о двух вещах: результат - целое число, а аргумент - это своего рода контейнер. Напротив, когда я читаюx.len()
, я уже должен знать, чтоx
это какой-то контейнер, реализующий интерфейс или наследующий от класса, имеющего стандартlen()
. Свидетель путаницы мы иногда есть , когда класс , который не реализует отображение имеетget()
илиkeys()
метод, или то , что не файл имеетwrite()
метод.Говоря то же самое по-другому, я рассматриваю len как встроенную операцию . Я бы не хотел это потерять. Я не могу точно сказать, имели ли вы это в виду или нет, но 'def len (self): ...' определенно звучит так, как будто вы хотите понизить его до обычного метода. Я категорически против.
Второе обоснование Python, которое я обещал объяснить, - это причина, по которой я выбрал для поиска специальные методы,
__special__
а не простоspecial
. Я ожидал множества операций, которые классы могут захотеть переопределить, некоторые стандартные (например,__add__
или__getitem__
), некоторые не такие стандартные (например, pickle__reduce__
долгое время вообще не поддерживался в коде C). Я не хотел, чтобы в этих специальных операциях использовались обычные имена методов, потому что тогда уже существующие классы или классы, написанные пользователями без энциклопедической памяти для всех специальных методов, будут склонны случайно определять операции, которые они не собирались реализовать. , с возможными катастрофическими последствиями. Иван Крстич более кратко объяснил это в своем сообщении, которое пришло после того, как я все это написал.- --Гидо ван Россум (домашняя страница: http://www.python.org/~guido/ )
Насколько я понимаю, в некоторых случаях префиксная нотация просто имеет больше смысла (например, Duck.quack имеет больше смысла, чем quack (Duck) с лингвистической точки зрения). И снова функции позволяют использовать «интерфейсы».
В таком случае я предполагаю реализовать get_king_moves исключительно на основе первого пункта Гвидо. Но это по-прежнему оставляет много открытых вопросов, касающихся, скажем, реализации класса стека и очереди с аналогичными методами push и pop - должны ли они быть функциями или методами? (здесь я бы угадал функции, потому что я действительно хочу сигнализировать об интерфейсе push-pop)
TL; DR: Может ли кто-нибудь объяснить, какой должна быть стратегия принятия решения, когда использовать функции или методы?
источник
X.frob
илиX.__frob__
и автономныйfrob
.Ответы:
Мое общее правило таково - операция выполняется над объектом или объектом?
если это выполняется объектом, это должна быть операция-член. Если это можно применить и к другим вещам, или если это делается чем-то еще для объекта, тогда это должна быть функция (или, возможно, член чего-то еще).
При знакомстве с программированием традиционно (хотя реализация неверна) объекты описываются в терминах реальных объектов, таких как автомобили. Вы упомянули утку, так что давайте продолжим.
В контексте аналогии «объекты - реальные вещи» «правильно» добавить метод класса для всего, что может делать объект. Скажем, я хочу убить утку, могу ли я добавить к утке .kill ()? Нет ... насколько мне известно, животные не кончают жизнь самоубийством. Поэтому, если я хочу убить утку, я должен сделать следующее:
Отходя от этой аналогии, почему мы используем методы и классы? Потому что мы хотим содержать данные и, надеюсь, структурировать наш код таким образом, чтобы его можно было многократно использовать и расширять в будущем. Это подводит нас к концепции инкапсуляции, которая так дорога объектно-ориентированному дизайну.
Принцип инкапсуляции на самом деле сводится к тому, к чему это сводится: как дизайнер вы должны скрывать все, что касается реализации и внутренних компонентов класса, к которым не обязательно иметь доступ любой пользователь или другой разработчик. Поскольку мы имеем дело с экземплярами классов, это сводится к тому, «какие операции являются критическими в этом экземпляре ». Если операция не зависит от экземпляра, она не должна быть функцией-членом.
TL; DR : что сказал @Bryan. Если он работает с экземпляром и ему требуется доступ к данным, внутренним по отношению к экземпляру класса, это должна быть функция-член.
источник
Используйте класс, если хотите:
1) Изолируйте вызывающий код от деталей реализации - используя абстракцию и инкапсуляцию .
2) Если вы хотите быть заменяемыми для других объектов - пользуйтесь полиморфизмом .
3) Если вы хотите повторно использовать код для похожих объектов - пользуйтесь наследованием .
Используйте функцию для вызовов, которые имеют смысл для многих различных типов объектов - например, встроенные функции len и repr применяются ко многим типам объектов.
При этом выбор иногда сводится к вкусу. Подумайте о том, что наиболее удобно и читается при обычных звонках. Например, что лучше
(x.sin()**2 + y.cos()**2).sqrt()
илиsqrt(sin(x)**2 + cos(y)**2)
?источник
Вот простое практическое правило: если код воздействует на единственный экземпляр объекта, используйте метод. Еще лучше: используйте метод, если нет веской причины написать его как функцию.
В вашем конкретном примере вы хотите, чтобы он выглядел так:
Не думай слишком много. Всегда используйте методы до тех пор, пока не дойдете до того момента, когда вы скажете себе: «Нет смысла делать это методом», и в этом случае вы можете создать функцию.
источник
len()
также можно утверждать, что дизайн имеет смысл, хотя я лично считаю, что функции там тоже не будут такими уж плохими - у нас просто будет другое соглашение, котороеlen()
всегда должно возвращать целое число (но со всеми дополнительными проблемами backcomp я бы не стал защищать это и для python)Обычно я думаю о предмете как о человеке.
Атрибуты - это имя человека, рост, размер обуви и т. Д.
Методы и функции - это операции, которые может выполнять человек.
Если операцию может выполнить любой старый человек, не требуя ничего уникального для этого конкретного человека (и ничего не меняя для этого конкретного человека), то это функция и должна быть написана как таковая.
Если операция воздействует на человека (например, еда, ходьба, ...) или требует участия чего-то уникального для этого человека (например, танцы, написание книги ...), тогда это должен быть метод .
Конечно, не всегда легко перевести это в конкретный объект, с которым вы работаете, но я считаю, что это хороший способ подумать об этом.
источник
height(person)
, не такperson.height
ли?are_married(person1, person2)
? Это очень общий запрос, поэтому он должен быть функцией, а не методом.Как правило , я использую классы для реализации логического набора возможностей для каких - то вещей , так что в остальной части моей программы я могу рассуждать о вещах , не беспокоясь о всех маленьких проблемах , которые составляют его реализацию.
Все, что является частью этой базовой абстракции «что вы можете делать с вещью », обычно должно быть методом. Как правило, это включает все, что может изменить вещь , поскольку состояние внутренних данных обычно считается частным и не является частью логической идеи «что вы можете сделать с вещью ».
Когда вы приходите к повышению операций на уровне, особенно если они связаны с нескольких вещей , я считаю , что они, как правило , наиболее естественно выражены как функции, если они могут быть построены из общественной абстракции вещи без необходимости специального доступа к внутренним (если они» re методы какого-то другого объекта). Это имеет большое преимущество в том, что когда я решаю полностью переписать внутреннее устройство того, как работает моя вещь (без изменения интерфейса), у меня просто есть небольшой базовый набор методов, которые нужно переписать, а затем все внешние функции, написанные в терминах этих методов. будет просто работать. Я считаю, что настаивание на том, что все операции, связанные с классом X, являются методами класса X, приводит к чрезмерно сложным классам.
Однако это зависит от кода, который я пишу. Для некоторых программ я моделирую их как совокупность объектов, взаимодействие которых приводит к поведению программы; здесь наиболее важные функциональные возможности тесно связаны с одним объектом и поэтому реализованы в методах с разбросом вспомогательных функций. Для других программ наиболее важным является набор функций, управляющих данными, а классы используются только для реализации естественных «утиных типов», которыми манипулируют функции.
источник
Вы можете сказать, что «перед лицом двусмысленности откажитесь от соблазна угадать».
Однако это даже не предположение. Вы абсолютно уверены, что результаты обоих подходов одинаковы в том, что они решают вашу проблему.
Я считаю, что иметь несколько способов достижения целей - это хорошо. Я смиренно посоветую вам, как это уже сделали другие пользователи, использовать то, что «вкуснее» / кажется более интуитивным с точки зрения языка.
источник