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

Несколько конструкторов: путинский путь?

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

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

В 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.

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

4b9b3361

Ответ 1

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

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

Вы можете создавать статические методы или методы класса с помощью декоратора @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. ⚠ См. здесь.

Ответ 2

У вас не может быть нескольких конструкторов, но вы можете использовать несколько методов с наименьшим количеством имен factory.

class Document(object):

    def __init__(self, whatever args you need):
        """Do not invoke directly. Use from_NNN methods."""
        # Implementation is likely a mix of A and B approaches. 

    @classmethod
    def from_string(cls, string):
        # Do any necessary preparations, use the `string`
        return cls(...)

    @classmethod
    def from_json_file(cls, file_object):
        # Read and interpret the file as you want
        return cls(...)

    @classmethod
    def from_docx_file(cls, file_object):
        # Read and interpret the file as you want, differently.
        return cls(...)

    # etc.

Однако вы не можете легко запретить пользователю использовать конструктор. (Если это важно, в качестве меры предосторожности во время разработки, вы можете проанализировать стек вызовов в конструкторе и проверить, что вызов сделан из одного из ожидаемых методов.)

Ответ 3

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

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

Ответ 4

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

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

Ответ 5

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

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 {}

Ответ 6

Если вы находитесь на Python 3.4+, вы можете использовать functools.singledispatch decorator для этого (с небольшой дополнительной помощью от methoddispatch decorator, который @ZeroPiraeus написал для его ответ):

class Container:

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

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

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

Ответ 7

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

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