Функция модуля 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, но не может вызвать метод для экземпляров класса.
Могу ли я это высказать? Это не проблема? Пожалуйста, помогите!
Ответы
Ответ 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
.