В Python, когда я должен использовать функцию вместо метода? - PullRequest
102 голосов
/ 13 ноября 2011

Zen of Python утверждает, что должен быть только один способ сделать что-то, но часто я сталкиваюсь с проблемой принятия решения, когда использовать функцию, а не когда использовать метод.

Давайте возьмем тривиальныйпример - объект ChessBoard.Допустим, нам нужен какой-то способ получить все легальные ходы Кинга, доступные на доске.Пишем ли мы ChessBoard.get_king_moves () или get_king_moves (chess_board)?

Вот несколько связанных вопросов, на которые я посмотрел:

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

Почему Python использует методы для некоторых функций (например, list.index ()), но функции для других (например, len (список))?

Основная причина - история.Функции использовались для тех операций, которые были общими для группы типов и предназначались для работы даже с объектами, у которых вообще не было методов (например, кортежей).Также удобно иметь функцию, которую можно легко применить к аморфному набору объектов, когда вы используете функциональные возможности Python (map (), apply () и др.).

Фактически, реализация len(), max (), min () как встроенная функция на самом деле меньше кода, чем их реализация в качестве методов для каждого типа.Можно спорить об отдельных случаях, но это часть Python, и уже слишком поздно вносить такие фундаментальные изменения.Функции должны оставаться, чтобы избежать массивного взлома кода.

Хотя это интересно, приведенное выше не особо говорит о том, какую стратегию принять.

Это одиниз причин - с пользовательскими методами разработчики могут свободно выбирать другое имя метода, например getLength (), length (), getlength () или что-то еще.Python обеспечивает строгое именование, так что можно использовать общую функцию len ().

Чуть интереснее.Я полагаю, что в некотором смысле функции - это Python-версия интерфейсов.

Наконец, от самого Гвидо :

Talkingнасчет Способностей / Интерфейсов заставил меня задуматься о некоторых из наших "мошеннических" имен специальных методов.В справочнике по языку говорится: «Класс может реализовывать определенные операции, которые вызываются специальным синтаксисом (например, арифметические операции или подписывание и разрезание) путем определения методов со специальными именами».Но есть все эти методы со специальными именами, такими как __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, с неуклюжостью doing то же самое, используя необработанную запись OO.

(б) Когда я читаю код, который говорит len(x) Я знаю , что он просит длина чего-то. Это говорит мне о двух вещах: результат целое число, а аргумент является своего рода контейнером. Иначе, когда я читаю x.len(), я уже должен знать, что x является чем-то вроде контейнер, реализующий интерфейс или наследующий от класса, имеет стандарт len(). Станьте свидетелем путаницы, которую мы иногда имеем класс, который не реализует отображение, имеет get() или keys() метод, или что-то, что не является файлом, имеет метод write().

Говоря то же самое по-другому, я вижу 'len' как встроенный работа . Я бы не хотел потерять это. Я не могу точно сказать, имели ли вы в виду это или нет, но «def len (self): ...» звучит как вы хочу понизить его до обычного метода. Я сильно -1 на этом.

Вторая часть объяснения Python, которое я обещал объяснить, является причиной почему я выбрал специальные методы, чтобы смотреть __special__, а не просто special. Я ожидал много операций, которые могли бы хотеть классы переопределить, некоторые стандартные (например, __add__ или __getitem__), некоторые не очень стандарт (например, __reduce__ от pickle долгое время не имел поддержки в C код вообще). Я не хотел, чтобы эти специальные операции использовали обычные имена методов, потому что тогда уже существующие классы или классы, написанные пользователи без энциклопедической памяти для всех специальных методов, будет склонен случайно определить операции, которые они не хотели осуществлять с возможными катастрофическими последствиями. Иван Крстич объяснил это более кратко в своем сообщении, которое пришло после того, как я записал все это.

- - Гидо ван Россум (домашняя страница: http://www.python.org/~guido/)

Мое понимание этого заключается в том, что в некоторых случаях префиксная нотация имеет больше смысла (т. Е. Duck.quack имеет больше смысла, чем кряк (Duck) с лингвистической точки зрения.) И опять же, функции допускают «интерфейсы».

В таком случае, я бы предположил реализовать get_king_moves, основываясь исключительно на первом пункте Гвидо. Но это все еще оставляет много открытых вопросов относительно, скажем, реализации стека и класса очереди с подобными методами push и pop - должны ли они быть функциями или методами? (здесь я бы угадал функции, потому что я действительно хочу сигнализировать об интерфейсе push-pop)

TLDR: Может ли кто-нибудь объяснить, какой должна быть стратегия для решения, когда использовать функции и методы?

Ответы [ 6 ]

70 голосов
/ 13 ноября 2011

Мое общее правило таково: - это операция, выполняемая над объектом или объектом?

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

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

class duck: 
    def __init__(self):pass
    def eat(self, o): pass 
    def crap(self) : pass
    def die(self)
    ....

В контексте аналогии «объекты - это реальные вещи», «правильно» добавлять метод класса для всего, что может сделать объект,Скажем, я хочу убить утку, добавить ли в нее .kill ()?Нет ... насколько я знаю, животные не совершают самоубийства.Поэтому, если я хочу убить утку, я должен сделать следующее:

def kill(o):
    if isinstance(o, duck):
        o.die()
    elif isinstance(o, dog):
        print "WHY????"
        o.die()
    elif isinstance(o, nyancat):
        raise Exception("NYAN "*9001)
    else:
       print "can't kill it."

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

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

TL; DR : что сказал @Bryan.Если он работает с экземпляром и нуждается в доступе к данным, которые являются внутренними по отношению к экземпляру класса, он должен быть функцией-членом.

25 голосов
/ 13 ноября 2011

Используйте класс, если хотите:

1) Изолировать вызывающий код от деталей реализации - используя преимущества инкапсуляции и .

2) Когда вы хотите заменить другие объекты - используйте полиморфизм .

3) Когда вы хотите повторно использовать код для похожих объектов - используйте наследование .

Используйте функцию для вызовов, которые имеют смысл для множества различных типов объектов - например, встроенные len и repr функции применяются ко многим объектам.

При этом выбор иногда сводится к вопросу вкуса.Подумайте о том, что является наиболее удобным и читаемым для типичных вызовов.Например, что было бы лучше (x.sin()**2 + y.cos()**2).sqrt() или sqrt(sin(x)**2 + cos(y)**2)?

7 голосов
/ 23 октября 2013

Я обычно думаю о предмете как о человеке.

Атрибуты - имя, рост, размер обуви и т. Д.

Методы и функции - это операции, которые может выполнять человек.

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

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

Конечно, не всегда просто перевести это в конкретный объект, с которым вы работаете, но я считаю, что это хороший способ думать об этом.

7 голосов
/ 13 ноября 2011

Вот простое правило: если код действует на один экземпляр объекта, используйте метод.Еще лучше: используйте метод, если нет веской причины написать его как функцию.

В вашем конкретном примере вы хотите, чтобы это выглядело так:

chessboard = Chessboard()
...
chessboard.get_king_moves()

Не переоценивайте это.Всегда используйте методы, пока не наступит момент, когда вы скажете себе: «Не имеет смысла делать это методом», и в этом случае вы можете создать функцию.

4 голосов
/ 13 ноября 2011

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

Все, что является частью этой базовой абстракции «что вы можете сделать с вещью », обычно должно быть методом.Обычно это включает в себя все, что может изменить вещь , поскольку внутреннее состояние данных обычно считается закрытым, а не частью логической идеи "что вы можете сделать с вещью ".

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

Хотя это зависит от кода, который я пишу.Для некоторых программ я моделирую их как совокупность объектов, взаимодействие которых приводит к поведению программы;здесь наиболее важные функциональные возможности тесно связаны с одним объектом и реализуются в методах с разбросом вспомогательных функций.Для других программ наиболее важным компонентом является набор функций, которые манипулируют данными, а классы используются только для реализации естественных «типов утки», которыми манипулируют функции.

0 голосов
/ 12 мая 2018

Вы можете сказать, что "перед лицом двусмысленности откажитесь от искушения угадать".

Впрочем, это даже не догадка. Вы абсолютно уверены, что результаты обоих подходов одинаковы в том, что они решают вашу проблему.

Я считаю, что хорошо иметь несколько способов достижения целей. Я бы смиренно сказал вам, как это уже делали другие пользователи, использовать тот, который «вкуснее» / кажется более интуитивным , с точки зрения языка.

...