Должен ли я использовать класс или словарь? - PullRequest
80 голосов
/ 28 октября 2010

У меня есть класс, который содержит только поля и не содержит методов, например:

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__. Так будет ли польза от использования диктата? Дикт будет быстрее, чем класс? И быстрее, чем класс со слотами?

Ответы [ 9 ]

37 голосов
/ 28 октября 2010

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

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

Различия в производительности здесь будут минимальными, хотя я бы удивился, если бы словарь не был быстрее.

35 голосов
/ 28 октября 2010

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

  • Вся ваша логика живет в одной функции
  • Легко обновляется и остается инкапсулированным
  • Если вы что-то измените позже, вы можете легко сохранить интерфейс
21 голосов
/ 28 октября 2010

С какой стати ты сделал этот словарь? В чем преимущество? Что произойдет, если позже вы захотите добавить код? Куда бы ваш код __init__ пошел?

Классы предназначены для объединения связанных данных (и обычно кода).

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

Держите этот класс.

19 голосов
/ 30 апреля 2013

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

Я сравнил время, необходимое для создания и изменения переменной в dict, классе new_style и классе 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 (вы всегда можете просто сделать функцию, похожую на метод). Но, если вам действительно нужны классы, помните - всегда используйте __ slots __ .

Примечание:

Я протестировал класс с помощью и классов new_style и old_style. Оказывается, классы old_style быстрее создаются, но медленнее модифицируются (не так много, но важно, если вы создаете много классов в узком цикле (совет: вы делаете это неправильно)).

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

Edit:

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

Если я изменим функцию dict , включив в нее ключи и значения и возвращая dict вместо переменной, содержащей dict, когда я его создаю, он дает мне 0,65 вместо 0,8 секунды.

class Foo(dict):
    pass

Создание похоже на класс со слотами, а изменение переменной - самое медленное (0,17 секунды), поэтому не используйте эти классы . перейти на dict (скорость) или класс, производный от объекта («синтаксис конфеты»)

10 голосов
/ 28 октября 2010

Я согласен с @adw. Я никогда не представлял бы «объект» (в смысле ОО) со словарем. Словари объединяют пары имя / значение. Классы представляют объекты. Я видел код, в котором объекты представлены словарями, и неясно, какова реальная форма вещи. Что происходит, когда определенные имя / значения отсутствуют? Что мешает клиенту вообще что-то вкладывать. Или вообще пытаться что-то вытащить. Форма вещи всегда должна быть четко определена.

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

5 голосов
/ 28 октября 2010

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

Следовательно, придерживайтесь класса.

3 голосов
/ 28 сентября 2012

Если все, что вы хотите получить, это синтаксическая конфета, такая как obj.bla = 5 вместо obj['bla'] = 5, особенно если вам приходится повторять это много, вы, возможно, захотите использовать какой-нибудь простой класс контейнера, как в предложении martineaus. Тем не менее код там довольно раздутый и излишне медленный. Вы можете сделать это просто так:

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

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

В любом случае, в вашем конкретном случае, похоже, нет никаких мотивов для отказа от вашей текущей реализации. Похоже, что вы не поддерживаете миллионы этих объектов, поэтому списочные типы не требуются. И на самом деле он содержит некоторую функциональную логику внутри __init__, поэтому вам также не стоит получать с AttrDict.

3 голосов
/ 29 октября 2010

Возможно, вам удастся съесть свой торт и съесть его. Другими словами, вы можете создать нечто, обеспечивающее функциональность как экземпляра класса, так и словаря. См. Рецепт ActiveState Dᴡɪᴛʜ ᴡɪᴛʜ ᴀᴛᴛʀɪʙᴜᴛᴇ-sᴛʏʟᴇ ᴀᴄᴄᴇss и комментарии о том, как это сделать.

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

Наконец, следует отметить, что имена членов класса должны быть допустимыми идентификаторами Python, а ключи словаря - нет, поэтому словарь обеспечит большую свободу в этом отношении, потому что ключи могут быть что угодно хешируемыми (даже что-то, что не является строкой) .

Обновление

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

0 голосов
/ 27 ноября 2017
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...