Лучше предварительно инициализировать атрибуты в классе или добавлять их по пути?

11

Извините, если это АБСОЛЮТНО софомальный вопрос, но мне любопытно, какие есть лучшие практики, и я не могу найти хорошего ответа в Google.

В Python я обычно использую пустой класс в качестве контейнера структуры данных super-catchall (вроде как файл JSON) и добавляю атрибуты по пути:

class DataObj:
    "Catch-all data object"
    def __init__(self):
        pass

def processData(inputs):
    data = DataObj()
    data.a = 1
    data.b = "sym"
    data.c = [2,5,2,1]

Это дает мне огромную гибкость, потому что контейнерный объект может по существу хранить что угодно. Поэтому, если возникнут новые требования, я просто добавлю его в качестве другого атрибута в объект DataObj (который я передаю в своем коде).

Однако недавно я (программисты на FP) убедился, что это ужасная практика, потому что очень трудно читать код. Нужно пройти весь код, чтобы выяснить, какие атрибуты на самом деле имеет DataObj.

Вопрос : Как я могу переписать это для большей ремонтопригодности, не жертвуя гибкостью?

Есть ли какие-нибудь идеи из функционального программирования, которые я могу перенять?

Я ищу лучшие практики там.

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

class DataObj:
    "Catch-all data object"
    def __init__(self):
        data.a = 0
        data.b = ""
        data.c = []

def processData(inputs):
    data = DataObj()
    data.a = 1
    data.b = "sym"
    data.c = [2,5,2,1]

Это на самом деле хорошая идея? Что если я не знаю, какие у меня атрибуты априори?

Gilead
источник
Ваши структуры данных настолько изменчивы, что вы, кажется, обеспокоены их ремонтопригодностью. В свободное от работы время ™ попробуйте прочитать эту статью о неизменных моделях данных . Это может полностью изменить ваше мнение о данных.
9000
@ 9000 Такая статья еще раз убеждает уже убежденных. Для меня это больше походило на практические рекомендации, чем на то, почему (список причин не очень убедителен, если вы не чувствуете, что у вас есть эти конкретные потребности). Для меня это не убеждает кого-то, обновляющего счета в VB, что необходимость постоянно делать новые копии своего объекта счета имеет смысл (добавить платеж, новый объект счета; добавить часть, новый объект счета).
Пол

Ответы:

10

Как я могу переписать это для большей ремонтопригодности, не жертвуя гибкостью?

Вы не Именно гибкость и является причиной проблемы. Если какой-либо код в любом месте может изменить какие атрибуты у объекта, ремонтопригодность уже разбита на части. В идеале, у каждого класса есть набор атрибутов, которые устанавливаются в камне после __init__и одинаковы для каждого экземпляра. Не всегда возможно или разумно, но это должно иметь место, когда у вас нет действительно веских причин избегать этого.

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

Это не очень хорошая идея. Конечно, тогда атрибут есть, но может иметь фиктивное значение или даже действительное значение, которое скрывает код, не присваивающий значение (или код с ошибкой). AttributeErrorэто страшно, но получить неправильные результаты хуже. Значения по умолчанию в целом хороши, но чтобы выбрать разумное значение по умолчанию (и решить, что требуется), вам необходимо знать, для чего используется объект.

Что если я не знаю, какие у меня атрибуты априори?

Тогда вы в любом случае облажались и должны использовать вхождение или список вместо жестких кодов имен атрибутов. Но я так понимаю, вы имели в виду «... в то время, когда я пишу контейнерный класс». Тогда ответ таков: «Вы можете редактировать файлы прямо сейчас». Нужен новый атрибут? Добавьте атрибут frigging к классу контейнера. Есть больше кода, использующего этот класс, и ему не нужен этот атрибут? Подумайте о том, чтобы разделить вещи на два отдельных класса (используйте mixins, чтобы остаться СУХИМЫМИ), поэтому сделайте это необязательным, если это имеет смысл.

Если вы боитесь писать повторяющиеся классы контейнеров: применяйте метапрограммирование разумно или используйте, collections.namedtupleесли вам не нужно мутировать членов после создания (ваши приятели по FP будут рады).


источник
7

Вы всегда можете использовать урок Алекса Мартелли . В твоем случае:

class DataObj:
    "Catch-all data object"
    def __init__(self, **kwds):
        self.__dict__.update(kwds)

def processData(inputs):
    data = DataObj(a=1, b="sym", c=[2,5,2,1])

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

И да, делать это таким образом - иногда хорошая идея.

pillmuncher
источник
1

Я, вероятно, использовал бы второй подход, возможно, используя Noneдля указания неверных данных. Это правда, что трудно читать / поддерживать, если вы добавите атрибуты позже. Однако дополнительная информация о назначении этого класса / объекта даст представление о том, почему первая идея - плохой дизайн: где у вас когда-нибудь будет полностью пустой класс без методов или данных по умолчанию? Почему вы не знаете, какие атрибуты у класса?

Это возможно , что processDataможет быть лучше в качестве метода ( process_dataследовать питон именованию), так как он действует на класс. Учитывая пример, похоже, что это может быть лучше в качестве структуры данных (где dictможет быть достаточно).

Приведя реальный пример, вы можете рассмотреть вопрос о передаче CodeReview , где они могут помочь в рефакторинге кода.

Кейси Кубалл
источник