Чи є кращою практикою попередньо ініціалізувати атрибути в класі чи додавати їх по дорозі?


11

Вибачте, якщо це АБСОЛЮТНО-софістичне питання, але мені цікаво, які найкращі практики є там, і я не можу знайти хорошу відповідь у Google.

У Python я зазвичай використовую порожній клас як контейнер структури суперкачальних даних (на зразок файлу 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]

Це насправді гарна ідея? Що робити, якщо я не знаю, які мої атрибути є апріорі?


Ваші структури даних настільки видозмінені, що, здається, ви стурбовані їх ремонтом. У свій багатий вільний час ™ спробуйте прочитати цю статтю про незмінні моделі даних . Це може повністю змінити спосіб міркування щодо даних.
9000

@ 9000 Така стаття знову переконує вже переконаного. Мені це здавалося скоріше як "як", ніж "чому" (Список "whys" насправді не переконливий, якщо ви не відчуваєте, що у вас є ці конкретні потреби). Мені це не переконує когось, що оновлює рахунки в VB, що необхідність постійно робити нові копії об'єкта рахунків має сенс (додавати платіж, новий об’єкт рахунку; додавати частину, новий рахунок-фактура).
Поль

Відповіді:


10

Як я можу переписати це для більшої ремонтоздатності, не приносячи шкоди гнучкості?

Ви цього не робите. Гнучкість - саме те, що викликає проблему. Якщо будь-який код де-небудь може змінити те, що атрибути об'єкта мають, технічне обслуговування вже складене. В ідеалі кожен клас має набір атрибутів, встановлених у камені після, __init__і однаковий для кожного примірника. Це не завжди можливо або розумно, але це має бути так, коли у вас немає поважних причин, щоб цього уникати.

одна ідея - попередньо ініціалізувати клас з усіма атрибутами, які очікують зустріти

Це не дуже гарна ідея. Зрозуміло, тоді атрибут є, але він може мати помилкове значення або навіть дійсне, яке прикриває код, який не присвоює значення (або неправильно написане). AttributeErrorстрашно, але гірші результати - гірше. Значення за замовчуванням в цілому добре, але щоб вибрати розумний за замовчуванням (і вирішити, що потрібно), потрібно знати, для чого використовується об’єкт.

Що робити, якщо я не знаю, які мої атрибути є апріорі?

Тоді ви в будь-якому випадку накручуєтесь, і вам слід використовувати дикт або список замість імен атрибутів жорсткого кодування. Але я вважаю, ви мали на увазі "... під час написання класу контейнерів". Тоді відповідь: "Ви можете редагувати файли в режимі блокування, так". Потрібен новий атрибут? Додайте неабиякий атрибут до класу контейнерів. У цьому класі використовується більше коду, і цей атрибут не потребує? Розгляньте розділення речей на два окремі класи (використовуйте міксини, щоб залишати СУХО), тому зробіть це необов’язковим, якщо це має сенс.

Якщо ви боїтесь писати повторювані класи контейнерів: Застосовуйте метапрограмування розумно або використовуйте, 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])

Таким чином, принаймні читачеві зрозуміло, що дані - це просто німий сховище даних, і можна відразу побачити, які значення зберігаються в одному і тому ж імені, оскільки все відбувається в одному рядку.

І так, інакше робити такі речі насправді хороша ідея.


1

Я, швидше за все, використовую другий підхід, можливо, використовуючи Noneдля вказівки недійсних даних. Це правда, що важко читати / підтримувати, якщо ви додаєте атрибути пізніше. Однак додаткова інформація про призначення цього класу / об'єкта дасть зрозуміти, чому перша ідея - це погана конструкція: де ви коли-небудь мали б абсолютно порожній клас без методів чи даних за замовчуванням? Чому б ви не знали, якими атрибутами є клас?

Цілком можливо, що це processDataможе бути кращим як метод ( process_dataдотримуватися конвенцій іменування python), оскільки він діє на клас. З огляду на приклад, схоже, що це може бути краще як структура даних (де dictможе бути достатньо).

Надаючи реальний приклад, ви можете розглянути питання про те , щоб передати це питання CodeReview , де вони могли б допомогти переробити код.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.