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

Использование статических методов в 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 способ найти данные сущности, поэтому я предпочел бы сохранить эту логику отдельно. Есть смысл?

4b9b3361

Ответ 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

Я всегда стараюсь думать, какое имя будет указывать на кого-то (а) без знания системы, и (б) знание других частей системы - не сказать, что я всегда успешный!