Должен ли я использовать класс или словарь?


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

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 64

9 ответов:

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

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

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

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

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

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

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

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

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

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

Я сравнил время, необходимое для создания и изменения переменной в 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

изменение a переменная...

Dict: 0.0735140918748
Class: 0.111714198313
Class_with_slots: 0.10618612142

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

Примечание:

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

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

Edit:

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

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

class Foo(dict):
    pass

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

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

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

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

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

Это может быть возможно, чтобы иметь свой торт и съесть его, тоже. Другими словами, вы можете создать что-то, что обеспечивает функциональность как класс и экземпляр словаря. Смотрите ActiveState в словарь с доступом в стиле атрибута рецепт и комментарии о способы сделать это.

Если вы решите использовать обычный класс, а не подкласс, я нашел 'простой, но удобный " коллекционер кучу именованных вещей" класс будет очень гибкий и полезный для того, что вы делаете (т. е. создаете относительный простой агрегатор информации). Поскольку это класс, вы можете легко расширить ее функциональность путем добавления методов.

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

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

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

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

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