Подтвердить что ты не робот

Функция модуля vs staticmethod vs classmethod vs no decorators: какая идиома более питонична?

Я разработчик Java, который играл и запускал Python. Недавно я наткнулся на эту статью, в которой упоминаются распространенные ошибки, которые программисты Java делают, когда они забирают Python. Первый попался на глаза:

Статический метод в Java не переводит на класс класса Python. О, конечно, это приводит к более или менее такому же эффекту, но цель classmethod - фактически делать то, что обычно не возможно даже в Java (например, наследование конструктора, отличного от стандартного). Идиоматический перевод статического метода Java обычно является функцией уровня модуля, а не классом или статическим методом. (И статические конечные поля должны переводить на константы уровня модуля.)

Это не большая проблема производительности, но программист на Python, который должен работать с кодом Java-идиомы, подобным этому, будет довольно раздражен, набрав Foo.Foo.someMethod, когда он должен быть просто Foo.someFunction. Но имейте в виду, что вызов метода класса предполагает дополнительное выделение памяти, которое вызывает метод static или метод.

О, и все эти цепи атрибутов Foo.Bar.Baz также не предоставляются бесплатно. В Java эти точечные имена просматриваются компилятором, поэтому во время выполнения действительно не имеет значения, сколько из них у вас есть. В Python поиск выполняется во время выполнения, поэтому каждая точка подсчитывается. (Помните, что в Python "Плоский лучше, чем вложенный", хотя он больше связан с "Подсчитываемыми коэффициентами" и "Простым лучше, чем сложным", чем с представлением о производительности.)

Я нашел это немного странным, потому что документация для staticmethod говорит:

Статические методы в Python аналогичны тем, которые существуют в Java или С++. Также см. Classmethod() для варианта, который полезен для создания альтернативных конструкторов классов.

Еще более озадачивающим является то, что этот код:

class A:
    def foo(x):
        print(x)
A.foo(5)

Сбой, как и ожидалось, в Python 2.7.3, но отлично работает в 3.2.3 (хотя вы не можете вызывать метод в экземпляре A только для класса.)

Итак, существует три способа реализации статических методов (четыре, если вы считаете, используя метод класса), каждый из которых имеет тонкие отличия, один из которых, по-видимому, недокументирован. Это, похоже, противоречит мантре Python . Должен быть один - и желательно только один - простой способ сделать это. Какая идиома является самой Pythonic? Каковы плюсы и минусы каждого?

Вот что я понимаю до сих пор:

Функция модуля:

  • Предотвращает проблему Foo.Foo.f()
  • Загрязняет пространство имен модулей больше, чем альтернативы
  • Наследование отсутствует

STATICMETHOD:

  • Сохраняет функции, связанные с классом внутри класса и из пространства имен модулей.
  • Позволяет вызывать функцию в экземплярах класса.
  • Подклассы могут переопределять метод.

classmethod:

  • Идентично для staticmethod, но также передает класс в качестве первого аргумента.

Обычный метод (только для Python 3):

  • Идентичен для staticmethod, но не может вызвать метод для экземпляров класса.

Могу ли я это высказать? Это не проблема? Пожалуйста, помогите!

4b9b3361

Ответ 1

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

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

Ответ 2

Отличный ответ BrenBarn, но я бы изменил "Если ему не нужен доступ к классу или экземпляру, сделайте его функцией":

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

иначе сделать его функцией модуля

Ответ 3

На самом деле это не ответ, а длинный комментарий:

Еще более озадачивающим является то, что этот код:

        class A:
            def foo(x):
                print(x)
        A.foo(5)

Сбой, как и ожидалось, в Python 2.7.3, но отлично работает в 3.2.3 (хотя вы не можете вызвать метод в экземпляре A, только в классе.)

Я попытаюсь объяснить, что здесь происходит.

Это, строго говоря, злоупотребление обычным протоколом метода экземпляра.

Здесь вы определяете метод, но с первым (и единственным) параметром, не названным self, но x. Конечно, вы можете вызвать метод в экземпляре A, но вам придется называть его следующим образом:

A().foo()

или

a = A()
a.foo()

поэтому экземпляр присваивается функции как первый аргумент.

Возможность вызова обычных методов через класс всегда была там и работает

a = A()
A.foo(a)

Здесь, когда вы вызываете метод класса, а не экземпляр, он не получает свой первый параметр, заданный автоматически, но вы должны его предоставить.

Пока это экземпляр A, все в порядке. Предоставление ему чего-то другого - это злоупотребление протоколом IMO и, следовательно, разница между Py2 и Py3:

В Py2, A.foo преобразуется в несвязанный метод и поэтому требует, чтобы его первый аргумент был экземпляром класса, в котором он "живет". Вызов его с чем-то другим завершится неудачно.

В Py3 эта проверка была удалена, а A.foo - только исходный объект функции. Поэтому вы можете назвать это всем, как первый аргумент, но я бы этого не сделал. Первый параметр метода всегда должен быть назван self и иметь семантику self.