Чи варто використовувати клас чи словник?


99

У мене є клас, який містить лише поля і відсутні методи, наприклад, такий:

class Request(object):

    def __init__(self, environ):
        self.environ = environ
        self.request_method = environ.get('REQUEST_METHOD', None)
        self.url_scheme = environ.get('wsgi.url_scheme', None)
        self.request_uri = wsgiref.util.request_uri(environ)
        self.path = environ.get('PATH_INFO', None)
        # ...

Це можна було легко перекласти на диктат. Клас є більш гнучким для майбутніх доповнень і може бути швидким __slots__. То чи буде користь від використання дикту замість цього? Чи буде диктант швидшим за клас? І швидше, ніж клас із слотами?


2
Я завжди використовую словники для зберігання даних, і мені це виглядає як випадок використання. в деяких випадках походження класу з dictможе мати сенс, до. акуратна перевага: коли ви налагодите, просто скажіть, print(request)і ви легко побачите всю інформацію про стан. при більш класичному підході вам доведеться писати свої власні __str__методи, що гасить, якщо вам доведеться це робити постійно.
потік

Вони є взаємозамінними stackoverflow.com/questions/1305532/…
Олексій

Якщо клас має повний сенс і зрозумілий для інших, чому б ні? Крім того, якщо ви, наприклад, визначаєте багато класів із загальними інтерфейсами, чому б ні? Але Python не підтримує потужні об'єктно-орієнтовані концепції, такі як C ++.
MasterControlProgram

3
@Ralf, що OOP не підтримує python?
qwr

Відповіді:


32

Навіщо ви зробили це словником? Яка перевага? Що станеться, якщо згодом ви хочете додати якийсь код? Куди __init__пішов би ваш код?

Класи призначені для поєднання пов'язаних даних (і зазвичай коду).

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

Тримайте цей клас.


Я створив би своєрідний заводський метод, що створює dict замість методу class ' __init__. Але ти маєш рацію: я б розділив речі, що належать разом.
Діамон

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

4
Це об'єктно-орієнтоване програмування, а не орієнтоване на клас з причини: ми маємо справу з об'єктами. Об'єкт характеризується 2 (3) властивостями: 1. стан (члени) 2. поведінка (методи) та 3. екземпляр можна описати кількома словами. Тому класи призначені для поєднання стану та поведінки.
friendzis

14
Я позначив це, оскільки завжди потрібно за замовчуванням простішу структуру даних, де це можливо. У цьому випадку для цільового призначення достатньо словника. Питання where would your __init__ code go?стосується. Це може переконати менш досвідченого розробника в тому, що слід використовувати тільки класи, оскільки метод init не використовується в словнику. Абсурд.
Ллойд Мур

1
@Ralf Клас Foo - це просто тип, як int та string. Чи зберігаєте ви значення в цілому типі або змінному foo цілого типу? Тонка, але важлива смислова різниця. У таких мовах, як C, ця відмінність не надто актуальна поза академічними колами. Хоча більшість мов OO мають підтримку змінної класу, що робить розмежування між класом та об'єктом / екземпляром надзвичайно актуальним - чи зберігаєте ви дані в класі (спільне для всіх примірників) або в певному об'єкті? Не
покусайте

44

Використовуйте словник, якщо вам не потрібен додатковий механізм класу. Ви також можете використовувати namedtupleгібридний підхід:

>>> from collections import namedtuple
>>> request = namedtuple("Request", "environ request_method url_scheme")
>>> request
<class '__main__.Request'>
>>> request.environ = "foo"
>>> request.environ
'foo'

Різниці в продуктивності тут будуть мінімальними, хоча я був би здивований, якби словник не був швидшим.


15
"Різниці в продуктивності тут будуть мінімальними , хоча я був би здивований, якби словник не був значно швидшим". Це не обчислює. :)
mipadi

1
@mipadi: це правда. Зараз виправлено: p
Катріель

Дикт в 1,5 рази швидший, ніж названий пар, і вдвічі швидший, ніж клас без слотів. Перевірте мій пост у цій відповіді.
alexpinho98

@ alexpinho98: Я дуже намагався знайти "пост", на який ви посилаєтесь, але не можу його знайти! чи можете ви надати URL-адресу. Дякую!
Ден Облінгер

@DanOblinger Я припускаю, що він має на увазі свою відповідь нижче.
Адам Льюїс

37

Клас пітона є диктатом під ним. У вас є деяка накладні витрати з поведінкою класу, але ви не зможете помітити це без профілера. У цьому випадку я вважаю, що ви користуєтесь класом, оскільки:

  • Вся ваша логіка живе в одній функції
  • Його легко оновлювати і залишатись капсульованим
  • Якщо щось змінити пізніше, ви легко зможете зберегти інтерфейс незмінним

Вся ваша логіка не живе в одній функції. Клас - це набір спільних станів і, як правило, один або кілька методів. Якщо ви змінюєте клас, вам не дають жодних гарантій щодо його інтерфейсу.
Ллойд Мур

25

Я думаю, що використання кожного з них є занадто суб'єктивним для мене, щоб зайнятися цим, тому я просто дотримуюся цифр.

Я порівняв час, необхідний для створення та зміни змінної у dict, класі new_style та class new_style зі слотами.

Ось код, який я використовував для його тестування (він трохи безладний, але він робить роботу.)

import timeit

class Foo(object):

    def __init__(self):

        self.foo1 = 'test'
        self.foo2 = 'test'
        self.foo3 = 'test'

def create_dict():

    foo_dict = {}
    foo_dict['foo1'] = 'test'
    foo_dict['foo2'] = 'test'
    foo_dict['foo3'] = 'test'

    return foo_dict

class Bar(object):
    __slots__ = ['foo1', 'foo2', 'foo3']

    def __init__(self):

        self.foo1 = 'test'
        self.foo2 = 'test'
        self.foo3 = 'test'

tmit = timeit.timeit

print 'Creating...\n'
print 'Dict: ' + str(tmit('create_dict()', 'from __main__ import create_dict'))
print 'Class: ' + str(tmit('Foo()', 'from __main__ import Foo'))
print 'Class with slots: ' + str(tmit('Bar()', 'from __main__ import Bar'))

print '\nChanging a variable...\n'

print 'Dict: ' + str((tmit('create_dict()[\'foo3\'] = "Changed"', 'from __main__ import create_dict') - tmit('create_dict()', 'from __main__ import create_dict')))
print 'Class: ' + str((tmit('Foo().foo3 = "Changed"', 'from __main__ import Foo') - tmit('Foo()', 'from __main__ import Foo')))
print 'Class with slots: ' + str((tmit('Bar().foo3 = "Changed"', 'from __main__ import Bar') - tmit('Bar()', 'from __main__ import Bar')))

І ось вихід ...

Створення ...

Dict: 0.817466186345
Class: 1.60829183597
Class_with_slots: 1.28776730003

Зміна змінної ...

Dict: 0.0735140918748
Class: 0.111714198313
Class_with_slots: 0.10618612142

Отже, якщо ви просто зберігаєте змінні, вам потрібна швидкість, і це не вимагатиме від вас багато обчислень, я рекомендую використовувати dict (ви завжди можете просто зробити функцію, схожу на метод). Але якщо вам справді потрібні заняття, пам’ятайте - завжди використовуйте __ слоти __ .

Примітка:

Я перевірив 'Клас' як з класами new_style, так і old_style. Виявляється, класи old_style створюються швидше, але повільніше змінюються (не набагато, але суттєво, якщо ви створюєте безліч класів в щільному циклі (порада: ви робите це неправильно)).

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

Редагувати:

Пізніше я протестував namedtuple: я не можу змінити його, але для створення 10000 зразків (або чогось подібного) це зайняло 1,4 секунди, тому словник справді найшвидший.

Якщо я зміню функцію dict, щоб включати ключі та значення та повертати дік замість змінної, що містить дікт, коли я створюю, це дає мені 0,65 замість 0,8 секунди.

class Foo(dict):
    pass

Створення - це як клас із слотами, а зміна змінної - найповільніша (0,17 секунди), тому не використовуйте ці класи . перейти на дікт (швидкість) або на клас, похідний від об'єкта ("синтаксична цукерка")


Я хотів би бачити номери для підкласу dict(без певних методів, мабуть?). Чи виконується це так само, як клас нового стилю, який був написаний з нуля?
Бенджамін Ходжсон

12

Я згоден з @adw. Я ніколи не представляв би «об’єкт» (в значенні ОО) зі словником. Словники сукупні пари імен / значень. Класи представляють об’єкти. Я бачив код, де об’єкти представлені словниками, і незрозуміло, що таке фактична форма речі. Що відбувається, коли певних імен / значень немає? Що обмежує клієнта взагалі нічого не ставити. Або намагатися взагалі щось витягнути. Форма речі завжди повинна бути чітко визначена.

Під час використання Python важливо будувати з дисципліною, оскільки мова дозволяє багатьом способам автору стріляти в ногу.


9
Відповіді на ТА сортуються за голосами, а відповіді з однаковим підрахунком голосів упорядковуються випадковим чином. Тож уточнюйте, кого ви маєте на увазі під останнім плакатом.
Майк Десимоне,

4
І звідки ви знаєте, що те, що має лише поля і не функціонує, є "об'єктом" в OO сенсі?
Мухаммед Алкароурі

1
Мій тест лакмусу "чи виправлена ​​структура цих даних?". Якщо так, то використовуйте об'єкт, інакше дікт. Відхід від цього лише створить плутанину.
weberc2

Ви повинні проаналізувати контекст проблеми, яку ви вирішуєте. Об'єкти повинні бути відносно очевидними, коли ви ознайомитесь із цим пейзажем. Особисто я вважаю, що повернення словника має бути вашим останнім засобом, якщо тільки те, що ви повертаєте, БУДЕ бути відображенням імен / значень пар. Я бачив занадто неохайний код, де все, що передано і вийшло з методів, - це лише словник. Це ліниво. Я люблю динамічно набрані мови, але також люблю, щоб мій код був чітким і логічним. Перенесення всього в словник може бути зручністю, яка приховує значення.
jaydel

5

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

Отже, дотримуйтесь класу.


2
Я абсолютно не згоден. Немає ніяких причин обмежувати використання словників лише речами, щоб повторити. Вони призначені для підтримки картографування.
Катріель

1
Прочитайте трохи уважніше, будь ласка. Я сказав, що, можливо, захоче перевернути цикл , а не буде . Для мене використання словника означає велику функціональну схожість між клавішами та значеннями. Наприклад, імена з віками. Використання класу означає, що різні "клавіші" мають значення з абсолютно різними значеннями. Наприклад, візьміть клас PErson. Тут 'name' буде рядком, а 'friends' може бути списком, словником або яким-небудь іншим відповідним об'єктом. Ви б не перебирали всі ці атрибути як частину звичайного використання цього класу.
Стигма

1
Я думаю, що в Python розрізнення класів і словників невиразно пояснюється тим, що перші реалізовані за допомогою другого («слоти» не витримують). Я знаю, що це мене трохи збентежило, коли я вперше вивчав мову (разом з тим, що класи були об'єктами, а отже, і примірниками якихось загадкових метакласів).
мартіно

4

Якщо все, що ви хочете досягти, це синтаксисні цукерки, як obj.bla = 5замість obj['bla'] = 5, особливо якщо вам доведеться багато разів повторювати, можливо, ви хочете використовувати якийсь звичайний клас контейнерів, як у пропозиціях мартинеїв. Тим не менш, код там досить роздутий і надмірно повільний. Ви можете просто так:

class AttrDict(dict):
    """ Syntax candy """
    __getattr__ = dict.__getitem__
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__

Ще одна причина переходу на namedtuple s або клас з __slots__може бути використання пам'яті. Дикти вимагають значно більше пам’яті, ніж типи списків, тому це може бути точкою, про яку слід задуматися.

У будь-якому випадку, у вашому конкретному випадку, здається, немає жодної мотивації відключитися від вашої поточної реалізації. Ви, здається, не підтримуєте мільйони цих об'єктів, тому ніяких типів похідних списків не потрібно. І він фактично містить деяку функціональну логіку всередині __init__, так що вам також не слід мати AttrDict.


types.SimpleNamespace(доступно з Python 3.3) є альтернативою користувальницькій AttrDict.
Крістіан Цюпіту

4

Можливо, ви зможете випити свій торт і з'їсти його теж. Іншими словами, ви можете створити щось, що забезпечує функціональність як класу, так і словника. Див. Dɪᴄᴛɪᴏɴᴀʀʏ ᴡɪᴛʜ ᴀᴛᴛʀɪʙᴜᴛᴇ-sᴛʏʟᴇ ᴀᴄᴄᴇss ActiveState рецепт і коментарі про способи зробити це.

Якщо ви вирішите використовувати звичайний клас, а не підклас, я знайшов рецепт Tʜᴇ sɪᴍᴘʟᴇ ʙᴜᴛ ʜᴀɴᴅʏ "ᴄᴏʟʟᴇᴄᴛᴏʀ ᴏғ ᴀ ʙᴜɴᴄʜ ᴏғ ɴᴀᴍᴇᴅ sᴛᴜғғ" ᴄʟᴀss (автор: Алекс Мартеллі) дуже хорошим гнучкий і корисний для такого роду виглядає так, як ви робите (тобто створюйте відносно простий агрегатор інформації). Оскільки це клас, ви легко можете додатково розширити його функціональність, додавши методи.

Нарешті, слід зазначити, що імена учасників класу мають бути законними ідентифікаторами Python, але ключі словника не роблять, тому словник надає більшу свободу в цьому відношенні, оскільки ключі можуть бути будь-якими хешувальними (навіть те, що не є рядком).

Оновлення

Клас object(який не має __dict__) підкласу з іменем SimpleNamespace(який його має) був доданий до typesмодуля Python 3.3 і є ще однією альтернативою.


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