Python 2.7: это хорошая практика - устанавливать все атрибуты args / kwargs с помощью setattr?

В этом ответе показано, как автоматически установить в качестве атрибута kwargs / args передан в класс __init__

Например, можно было сделать:

class Employee(object):
    def __init__(self, *initial_data, **kwargs):
        # EDIT
        self._allowed_attrs = ['name', 'surname', 'salary', 'address', 'phone', 'mail']

        for dictionary in initial_data:
            for key in dictionary:   
                if key in self._allowed_attrs:  # EDIT
                    setattr(self, key, dictionary[key])
        for key in kwargs:
            if key in self._allowed_attrs:  # EDIT
                setattr(self, key, kwargs[key])

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

Считается ли это хорошей практикой? Какие плюсы и минусы этого решения против инициализации каждого атрибута вручную? Есть ли другой предпочтительный подход?

РЕДАКТИРОВАТЬ: Поскольку первые комментарии / ответы (правильно) сосредоточены на очистке аргументов или перечислении аргументов, я думаю, что это может быть довольно легко решено в этой структуре.


person FLab    schedule 12.04.2017    source источник
comment
Из Zen of Python: Explicit is better than implicit.. Вы требуете, чтобы вызывающий абонент связал *args в словарь.   -  person AChampion    schedule 12.04.2017
comment
Спасибо за комментарий, но я бы сравнил это с фразами «Красивое лучше, чем уродливое» и «Удобочитаемость»: например, 25 строк типа 'self.something = kwargs [' something ']' мне не кажутся красивыми и удобочитаемыми, но это явно личный вкус   -  person FLab    schedule 12.04.2017
comment
Я бы не согласился с удобочитаемостью и выделил бы Sparse is better than dense, hard to explain и т. Д. В целом я бы не одобрил этот подход.   -  person AChampion    schedule 12.04.2017
comment
for dictionary in initial_data пахнет рыбой. *args - это список произвольных объектов, переданных в качестве аргументов, а не список dicts. Таким образом, это приведет к сбою, если все переданные аргументы, не являющиеся ключевыми словами, не являются dicts.   -  person ivan_pozdeev    schedule 19.04.2017


Ответы (4)


Предыдущее обсуждение: Декоратор Python для автоматического определения переменных __init__, Python: стоит ли динамически создавать переменные?

Плюсы:

  • Уменьшает дублирование кода

Минусы:

person ivan_pozdeev    schedule 19.04.2017

Вопрос: ... менее повторяющийся и более короткий код

В вашем примере кода требуется 9 строк и 28 ключевых слов.

class Employee(object):
    def __init__(self, name, surname, salary, address, phone, mail):
        self.name = name
        self.surname = surename
        self.salary = salary
        self.address = address
        self.phone = phone
        self.mail = mail

По умолчанию требуется 6 строк и 19 ключевых слов. Итак, вашему примеру требуется больше, а не «более короткий код». Я не вижу никакого "повторяющегося ... кода" в коде по умолчанию, все назначения выполняются один раз.

Сравнил эти две строчки, сделал то же самое. Определите, какие аргументы могут передаваться:

self._allowed_attrs = ['name', 'surname', 'salary', 'address', 'phone', 'mail']  

с участием

def __init__(self, name, surname, salary, address, phone, mail):

Второй требует меньше усилий и делает все за один раз.
Не требуется if key in self._allowed_attrs:, так как python сделает это за вас.


В реальном проекте я бы использовал что-то вроде этого

class Employee(object):
    def __init__(self, person, salary=None):
        self.id = unique_id()
        self.person = person
        self.salary = salary

Все данные, относящиеся к person, должны быть обобщены в object person.


Заключение:
Для данного примера class Employee я бы никогда не использовал (*args, **kwargs).
(*args, **kwargs) аргументы полезны только в том случае, если невозможно предсказать, какие аргументы будут переданы.

person stovfl    schedule 19.04.2017

Из-за этого невозможно понять, каких аргументов ожидает класс.

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

person RemcoGerlich    schedule 12.04.2017

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

Конечно, лучше, если вы позаботитесь о правильной обработке атрибутов и даже о дезинфекции содержимого, поэтому лучше всего иметь либо декоратор для метода __init__, либо его эквивалент в базовом классе __init__, который будет сделайте все, что нужно: проверьте, подходят ли переданные параметры для конкретного класса, а затем используйте setattr для установки их значений в экземпляре.

Я думаю, что менее волшебный способ - это иметь соглашение в иерархии классов для объявления требуемых параметров как атрибутов класса. Таким образом, вы можете использовать эти атрибуты класса для документирования ожидаемых параметров и их типов, сохранить подпись __init__ как *args, **kwargs, а инициализация вашего базового класса обработать их все.

Базовые модели SQLAlchemy делают это - вы указываете атрибуты класса как специальные «инструментированные атрибуты», и они автоматически назначаются при вызове в __init__.

Более простой способ:

_sentinel = object()

class Base(object):
    def __init__(self, *args, **kwargs):
        for attr_name, class_attr in self.__class__.__dict__.items():
            if isinstance(class_attr, type) and kwargs.get(attr_name, _sentinel) != _sentinel:
                attr_value = kwargs[attr_name]
                if not isinstance(attr_value, class_attr):
                    raise TypeError("Parameter {} is expected to be of type {}".format(attr_name, class_attr))
                setattr(self, attr_name, attr_value)


class Person(Base):
    name = str
    age = int
    phonenumber = Phone
    ...

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

person jsbueno    schedule 12.04.2017