пустой - конструктор__ init__ python




Несколько конструкторов: Pythonic путь? (5)

Большинство Pythonic будет то, что стандартная библиотека Python уже делает. Основной разработчик Раймонд Хеттингер (специалист по collections ) выступил с докладом на эту тему , а также дал общие рекомендации по написанию уроков.

Используйте отдельные функции уровня класса для инициализации экземпляров, например, как dict.fromkeys() не инициализатор класса, но все равно возвращает экземпляр dict . Это позволяет вам гибко подходить к нужным аргументам, не меняя сигнатуры методов при изменении требований.

На этот вопрос уже есть ответ здесь:

У меня есть контейнерный класс, который содержит данные. Когда контейнер создан, существуют разные методы для передачи данных.

  1. Передайте файл, который содержит данные
  2. Передать данные напрямую через аргументы
  3. Не передавайте данные; просто создайте пустой контейнер

В Java я бы создал три конструктора. Вот как бы это выглядело, если бы это было возможно в Python:

class Container:

    def __init__(self):
        self.timestamp = 0
        self.data = []
        self.metadata = {}

    def __init__(self, file):
        f = file.open()
        self.timestamp = f.get_timestamp()
        self.data = f.get_data()
        self.metadata = f.get_metadata()

    def __init__(self, timestamp, data, metadata):
        self.timestamp = timestamp
        self.data = data
        self.metadata = metadata

В Python я вижу три очевидных решения, но ни одно из них не является симпатичным:

A : Используя аргументы ключевого слова:

def __init__(self, **kwargs):
    if 'file' in kwargs:
        ...
    elif 'timestamp' in kwargs and 'data' in kwargs and 'metadata' in kwargs:
        ...
    else:
        ... create empty container

B : Использование аргументов по умолчанию:

def __init__(self, file=None, timestamp=None, data=None, metadata=None):
    if file:
        ...
    elif timestamp and data and metadata:
        ...
    else:
        ... create empty container

C : Предоставить только конструктор для создания пустых контейнеров. Предоставьте методы для заполнения контейнеров данными из разных источников.

def __init__(self):
    self.timestamp = 0
    self.data = []
    self.metadata = {}

def add_data_from_file(file):
    ...

def add_data(timestamp, data, metadata):
    ...

Решения A и B в основном одинаковы. Мне не нравится делать if / else, тем более что я должен проверить, были ли предоставлены все аргументы, необходимые для этого метода. A немного более гибок, чем B, если код должен быть расширен четвертым методом для добавления данных.

Решение C кажется самым хорошим, но пользователь должен знать, какой метод ему нужен. Например: он не может сделать c = Container(args) если он не знает, что такое args .

Какое самое питонское решение?


Вы не можете иметь несколько методов с одним и тем же именем в Python . Перегрузка функций - в отличие от Java - не поддерживается.

Используйте параметры по умолчанию или аргументы **kwargs и *args .

Вы можете создавать статические методы или методы класса с @staticmethod декоратора @staticmethod или @classmethod чтобы возвращать экземпляр вашего класса или добавлять другие конструкторы.

Я советую вам сделать:

class F:

    def __init__(self, timestamp=0, data=None, metadata=None):
        self.timestamp = timestamp
        self.data = list() if data is None else data
        self.metadata = dict() if metadata is None else metadata

    @classmethod
    def from_file(cls, path):
       _file = cls.get_file(path)
       timestamp = _file.get_timestamp()
       data = _file.get_data()
       metadata = _file.get_metadata()       
       return cls(timestamp, data, metadata)

    @classmethod
    def from_metadata(cls, timestamp, data, metadata):
        return cls(timestamp, data, metadata)

    @staticmethod
    def get_file(path):
        # ...
        pass

⚠ Никогда не используйте изменяемые типы по умолчанию в Python. ⚠ Смотри here .


Каковы системные цели для этого кода? С моей точки зрения, ваша критическая фраза состоит в but the user has to know which method he requires. Какой опыт вы хотите, чтобы ваши пользователи имели с вашим кодом? Это должно вести дизайн интерфейса.

Теперь перейдем к удобству обслуживания: какое решение проще всего читать и обслуживать? Опять же, я чувствую, что решение С уступает. Для большинства команд, с которыми я работал, решение B предпочтительнее, чем A: его немного легче читать и понимать, хотя обе легко разбиваются на небольшие блоки кода для лечения.


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

def __init__(self, timestamp=None, data=[], metadata={}):
    timestamp = time.now()

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

Вы можете принять еще больше необязательных аргументов, используя *args и **kwargs в конце списка аргументов.

def __init__(self, timestamp=None, data=[], metadata={}, *args, **kwards):
    if 'something' in kwargs:
        # do something

Я не уверен, правильно ли я понял, но разве это не сработает?

def __init__(self, file=None, timestamp=0, data=[], metadata={}):
    if file:
        ...
    else:
        self.timestamp = timestamp
        self.data = data
        self.metadata = metadata

Или вы могли бы даже сделать:

def __init__(self, file=None, timestamp=0, data=[], metadata={}):
    if file:
        # Implement get_data to return all the stuff as a tuple
        timestamp, data, metadata = f.get_data()

    self.timestamp = timestamp
    self.data = data
    self.metadata = metadata

Благодаря совету Джона Кипарски есть лучший способ избежать глобальных объявлений data и metadata так что это новый способ:

def __init__(self, file=None, timestamp=None, data=None, metadata=None):
    if file:
        # Implement get_data to return all the stuff as a tuple
        with open(file) as f:
            timestamp, data, metadata = f.get_data()

    self.timestamp = timestamp or 0
    self.data = data or []
    self.metadata = metadata or {}




idiomatic