Использование статических методов в python - лучшая практика
Когда и как статические методы предполагают использовать в python? Мы уже установили, используя метод класса, поскольку метод factory для создания экземпляра объекта следует избегать, когда это возможно. Другими словами, не рекомендуется использовать методы класса в качестве альтернативного конструктора (см. Factory метод для объекта python - наилучшая практика).
Допустим, у меня есть класс, используемый для представления данных сущности в базе данных. Представьте, что это объект dict
, содержащий имена полей и значения полей, а одно из полей - идентификационный номер, который делает данные уникальными.
class Entity(object):
def __init__(self, data, db_connection):
self._data = data
self._db_connection
Здесь мой метод __init__
принимает объект данных объекта dict
. Допустим, у меня есть только идентификационный номер, и я хочу создать экземпляр Entity
. Сначала мне нужно будет найти остальную часть данных, а затем создать экземпляр моего объекта Entity
. Из моего предыдущего вопроса мы установили, что, по возможности, следует избегать использования метода класса как метода factory.
class Entity(object):
@classmethod
def from_id(cls, id_number, db_connection):
filters = [['id', 'is', id_number]]
data = db_connection.find(filters)
return cls(data, db_connection)
def __init__(self, data, db_connection):
self._data = data
self._db_connection
# Create entity
entity = Entity.from_id(id_number, db_connection)
Выше приведен пример того, что не делать или, по крайней мере, не делать, если есть альтернатива. Теперь мне интересно, редактирование моего метода класса, так что это больше полезный метод, а метод factory является допустимым решением. Другими словами, соответствует ли следующий пример наилучшей практике использования статических методов.
class Entity(object):
@staticmethod
def data_from_id(id_number, db_connection):
filters = [['id', 'is', id_number]]
data = db_connection.find(filters)
return data
# Create entity
data = Entity.data_from_id(id_number, db_connection)
entity = Entity(data)
Или имеет смысл использовать автономную функцию для поиска данных сущности из идентификационного номера.
def find_data_from_id(id_number, db_connection):
filters = [['id', 'is', id_number]]
data = db_connection.find(filters)
return data
# Create entity.
data = find_data_from_id(id_number, db_connection)
entity = Entity(data, db_connection)
Примечание. Я не хочу менять свой метод __init__
. Раньше люди предлагали сделать мой метод __init__
похожим на этот __init__(self, data=None, id_number=None)
, но было бы 101 способ найти данные сущности, поэтому я предпочел бы сохранить эту логику отдельно. Есть смысл?
Ответы
Ответ 1
Когда и как статические методы предполагают использовать в python?
Ответ glib: Не очень часто.
Четный, но не совсем бесполезный ответ: когда они делают ваш код более удобочитаемым.
Во-первых, возьмите обход в документы:
Статические методы в Python аналогичны тем, которые существуют в Java или С++. Также см. classmethod()
для варианта, который полезен для создания альтернативных конструкторов классов.
Итак, когда вам нужен статический метод в С++, вам нужен статический метод в Python, правильно?
Ну, нет.
В Java нет функций, просто методов, поэтому вы создаете псевдоклассы, которые являются просто связями статических методов. Способ сделать то же самое в Python - просто использовать свободные функции.
Это довольно очевидно. Тем не менее, это хороший стиль Java, чтобы выглядеть как можно труднее для соответствующего класса, чтобы вставить в него функцию, поэтому вы можете избежать написания этих псевдоклассов, в то же время делая то же самое, это плохой стиль Python - снова используйте свободные функции - и это гораздо менее очевидно.
С++ не имеет того же ограничения, что и Java, но многие стили С++ довольно похожи. (С другой стороны, если вы программист "Современный С++", который усвоил слова "свободные функции, являющиеся частью интерфейса класса", ваши инстинкты для "где статические методы полезны", вероятно, довольно хороши для Python.)
Но если вы придете к этому с первых принципов, а не с другого языка, там есть более простой способ взглянуть на вещи:
A @staticmethod
- это в основном просто глобальная функция. Если у вас есть функция foo_module.bar()
, которая была бы более удобочитаемой по какой-либо причине, если бы она была написана как foo_module.BazClass.bar()
, сделайте ее @staticmethod
. Если нет, не делайте этого. Это действительно все, что нужно. Единственная проблема заключается в создании ваших инстинктов для того, что более читаемо для идиоматического программиста на Python.
И, конечно, используйте @classmethod
, когда вам нужен доступ к классу, но не конструкторы-альтернаторы-экземпляры - это парадигма для этого, как подразумевают документы. Хотя вы часто можете моделировать @classmethod
с помощью @staticmethod
, просто явно ссылаясь на класс (особенно если у вас нет большого подкласса), вы не должны.
Наконец, перейдя к вашему конкретному вопросу:
Если единственной причиной, по которой клиенты когда-либо должны искать данные по идентификатору, является создание Entity
, который звучит как деталь реализации, которую вы не должны раскрывать, а также делает клиентский код более сложным. Просто используйте конструктор. Если вы не хотите изменять свой __init__
(и вы правы, что есть веские причины, по которым вы не захотите), используйте @classmethod
в качестве альтернативного конструктора: Entity.from_id(id_number, db_connection)
.
С другой стороны, если этот поиск является тем, что по своей сути полезно для клиентов в других случаях, которые не имеют ничего общего с конструкцией Entity
, похоже, что это не имеет ничего общего с классом Entity
(или, по крайней мере, не более, чем что-либо еще в одном модуле). Итак, просто сделайте это свободной функцией.
Ответ 2
Ответ на связанный вопрос конкретно говорит об этом:
Метод @classmethod - это идиоматический способ сделать "альтернативный конструктор" - есть примеры по всему stdlib-itertools.chain.from_iterable, datetime.datetime.fromordinal и т.д.
Итак, я не знаю, как вы поняли, что использование метода class по своей сути плохое. Мне действительно нравится идея использования classmethod в вашей конкретной ситуации, поскольку она делает следующий код и использует api легко.
Альтернативой было бы использовать аргументы конструктора по умолчанию:
class Entity(object):
def __init__(self, id, db_connection, data=None):
self.id = id
self.db_connection = db_connection
if data is None:
self.data = self.from_id(id, db_connection)
else:
self.data = data
def from_id(cls, id_number, db_connection):
filters = [['id', 'is', id_number]]
return db_connection.find(filters)
Я предпочитаю версию classmethod, которую вы написали первоначально. Тем более, что data
довольно неоднозначно.
Ответ 3
Ваш первый пример имеет для меня наибольший смысл: Entity.from_id
довольно краткий и понятный.
Он избегает использования data
в следующих двух примерах, которые не описывают, что возвращается; data
используется для построения Entity
. Если вы хотите указать, что data
используется для создания Entity
, вы можете назвать свой метод чем-то вроде Entity.with_data_for_id
или эквивалентной функции entity_with_data_for_id
.
Использование глагола, такого как find
, также может быть довольно запутанным, поскольку оно не дает никаких указаний на возвращаемое значение - какова функция, которая должна выполняться при обнаружении данных? (Да, я понимаю, что str
имеет метод find
, не лучше ли он назван index_of
? Но тогда там также index
...) Это напоминает мне классику:
![find x]()
Я всегда стараюсь думать, какое имя будет указывать на кого-то (а) без знания системы, и (б) знание других частей системы - не сказать, что я всегда успешный!