131

I have a class that contains only fields and no methods, like this:

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)
        # ...

This could easily be translated to a dict. The class is more flexible for future additions and could be fast with __slots__. So would there be a benefit of using a dict instead? Would a dict be faster than a class? And faster than a class with slots?

martineau
  • 119,623
  • 25
  • 170
  • 301
deamon
  • 89,107
  • 111
  • 320
  • 448
  • 3
    i always use dictionaries for keeping data, and this looks like a use case to me. in some cases deriving a class from `dict` can make sense, to. neat advantage: when you debug, just say `print(request)` and you will readily see all state information. with the more classical approach you will have to write your custom `__str__` methods, which sucks if you have to do it all the time. – flow Feb 19 '11 at 19:43
  • 1
    They are interchangeable http://stackoverflow.com/questions/1305532/convert-python-dict-to-object – Oleksiy Mar 02 '13 at 01:44
  • If the class makes total sense and is clear to others, why not? Furthermore, if you define many classes with common interfaces for example, why not? But Python does not support mighty object oriented concepts like C++. – MasterControlProgram Feb 06 '17 at 20:07
  • 4
    @Ralf what OOP does python not support? – qwr Jan 25 '19 at 22:11

10 Answers10

50

Use a dictionary unless you need the extra mechanism of a class. You could also use a namedtuple for a hybrid approach:

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

Performance differences here will be minimal, although I would be surprised if the dictionary wasn't faster.

Katriel
  • 120,462
  • 19
  • 136
  • 170
  • 20
    "Performance differences here will be **minimal**, although I would be surprised if the dictionary wasn't **significantly** faster." This does not compute. :) – mipadi Oct 28 '10 at 16:49
  • 1
    @mipadi: this is true. Now fixed :p – Katriel Oct 28 '10 at 16:50
  • The dict is 1.5 times faster than the namedtuple and twice as fast as a class without slots. Check my post on this answer. – alexpinho98 Apr 30 '13 at 19:57
  • @alexpinho98: I have tried hard to find the 'post' you are referring to, but I cannot find it! can you provide the URL. Thanks! – Dan Oblinger May 26 '15 at 16:31
  • 1
    @DanOblinger I'm assuming he means his answer below. – Adam Lewis Oct 16 '15 at 21:03
  • @katrielalex, `namedtuple` is a factory that produces a *class*. So `request` in your example is a class. To create an instance from it, you should've written `r = request(...)`. After being initialized, namedtuple instances do not allow changing attributes. – arrakis_sun Apr 28 '17 at 15:06
44

A class in python is a dict underneath. You do get some overhead with the class behavior, but you won't be able to notice it without a profiler. In this case, I believe you benefit from the class because:

  • All your logic lives in a single function
  • It is easy to update and stays encapsulated
  • If you change anything later, you can easily keep the interface the same
Bruce Armstrong
  • 1,562
  • 10
  • 11
  • 1
    All your logic does not live in a single function. A class is a tuple of shared state and usually one or more methods. If you change a class, you are not given any guarantees about it's interface. – Lloyd Moore Mar 16 '16 at 12:58
43

Why would you make this a dictionary? What's the advantage? What happens if you later want to add some code? Where would your __init__ code go?

Classes are for bundling related data (and usually code).

Dictionaries are for storing key-value relationships, where usually the keys are all of the same type, and all the values are also of one type. Occasionally they can be useful for bundling data when the key/attribute names are not all known up front, but often this a sign that something's wrong with your design.

Keep this a class.

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
adw
  • 4,901
  • 1
  • 25
  • 18
  • I would create a kind of factory method creating a dict instead of the class' `__init__` method. But you're right: I would separate to things that belong together. – deamon Oct 28 '10 at 17:59
  • 109
    couldn't disagree with you more: dictionaries, sets, lists, and tuples are all there to bundle related data. in no way is there any supposition that the values of a dictionary should or must be of the same data type, quite the contrary. in many lists and sets, values will have the same type, but that is mostly because that's what we like to enumerate together. i actually do think that the widespread use of classes to keep data is an abuse of oop; when you tghink about serialization issues, you can easily see why. – flow Feb 19 '11 at 19:39
  • 4
    It's OBJECT oriented programming and not class oriented for a reason: we deal with objects. An object is characterized by 2 (3) properties: 1. state (members) 2. behavior (methods) and 3. instance can be described in several words. Therefore classes are for bundling together state and behavior. – friendzis Jul 11 '13 at 06:16
  • 20
    I've marked this down because you should always default to the simpler data structure where possible. In this case, a dictionary is sufficient for the intended purpose. The question `where would your __init__ code go?` is concerning. It could persuade a less experienced developer that only classes are to be used since an __init__ method is not used in a dictionary. Absurd. – Lloyd Moore Mar 16 '16 at 12:56
  • @friendzis we deal with objects which are defined by classes in most of the OO languages. The abstraction is possible by classes. Classes can be further specialized etc. But many OOP concepts are not available in Python. – MasterControlProgram Feb 06 '17 at 19:59
  • 1
    @Ralf A class Foo is merely a type, like int and string. Do you store value in an integer type or variable foo of integer type? Subtle, but important semantic difference. In languages like C this distinction is not too relevant outside of academic circles. Although most OO languages have class variable support, which makes the distinction between class and object/instance hugely relevant - do you store data in class (shared across all instances) or in a specific object? Do not get bitten – friendzis Feb 23 '17 at 08:48
  • @MasterControlProgram "many OOP concepts are not available in Python" not true. – juanpa.arrivillaga Oct 18 '22 at 23:21
  • The answer is full of contradictions. It asks for advantages of dicts but not of classes. It sais, classes are also for code. But the question does not have code. It is incorrect that dict values have typically the same type. Not knowing the keys upfront it is not a bad design, but how dynamic systems often are. Seeing future code in __init__ is pure speculation and also no problem with functions. The author personally prefers classes and tries to invent arbitrary justifications for this decision. This is how wrong design decisions happen. It looks like the author knows nothing else than OOP. – habrewning Apr 19 '23 at 18:47
30

I think that the usage of each one is way too subjective for me to get in on that, so i'll just stick to numbers.

I compared the time it takes to create and to change a variable in a dict, a new_style class and a new_style class with slots.

Here's the code i used to test it(it's a bit messy but it does the job.)

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')))

And here is the output...

Creating...

Dict: 0.817466186345
Class: 1.60829183597
Class_with_slots: 1.28776730003

Changing a variable...

Dict: 0.0735140918748
Class: 0.111714198313
Class_with_slots: 0.10618612142

So, if you're just storing variables, you need speed, and it won't require you to do many calculations, i recommend using a dict(you could always just make a function that looks like a method). But, if you really need classes, remember - always use __slots__.

Note:

I tested the 'Class' with both new_style and old_style classes. It turns out that old_style classes are faster to create but slower to modify(not by much but significant if you're creating lots of classes in a tight loop (tip: you're doing it wrong)).

Also the times for creating and changing variables may differ on your computer since mine is old and slow. Make sure you test it yourself to see the 'real' results.

Edit:

I later tested the namedtuple: i can't modify it but to create the 10000 samples (or something like that) it took 1.4 seconds so the dictionary is indeed the fastest.

If i change the dict function to include the keys and values and to return the dict instead of the variable containing the dict when i create it it gives me 0.65 instead of 0.8 seconds.

class Foo(dict):
    pass

Creating is like a class with slots and changing the variable is the slowest (0.17 seconds) so do not use these classes. go for a dict (speed) or for the class derived from object ('syntax candy')

alexpinho98
  • 909
  • 8
  • 14
  • I'd like to see the numbers for a subclass of `dict` (with no overridden methods, I guess?). Does it perform the same way as a new-style class that was written from scratch? – Benjamin Hodgson Apr 30 '13 at 20:09
  • I appreciate the effort but this misses this is mostly irrelevant. Python is not supposed to be fast, it's supposed to be readable. Over a few ms of performance I value: 1) clarity of intention, ease of rewriting, etc. If you want speed, use C++. – anon01 Jan 29 '21 at 07:54
14

I agree with @adw. I would never represent an "object" (in an OO sense) with a dictionary. Dictionaries aggregate name/value pairs. Classes represent objects. I've seen code where the objects are represented with dictionaries and it's unclear what the actual shape of the thing is. What happens when certain name/values aren't there? What restricts the client from putting anything at all in. Or trying to get anything at all out. The shape of the thing should always be clearly defined.

When using Python it is important to build with discipline as the language allows many ways for the author to shoot him/herself in the foot.

jaydel
  • 14,389
  • 14
  • 62
  • 98
  • 9
    Answers on SO are sorted by votes, and answers with the same vote count are randomly ordered. So please clarify whom you mean by "the last poster". – Mike DeSimone Oct 28 '10 at 17:34
  • 4
    And how do you know that something that has only fields and no functionality is an "object" in an OO sense? – Muhammad Alkarouri Oct 28 '10 at 18:55
  • 1
    My litmus test is "is the structure of this data fixed?". If yes, then use an object, otherwise a dict. Departing from this is only going to create confusion. – weberc2 May 20 '16 at 23:26
  • You have to analyze the context of the problem you are solving. Objects should be relatively obvious once you are familiar with that landscape. Personally I think returning a dictionary should be your last resort unless what you are returning SHOULD be a mapping of name/value pairs. I've seen too much sloppy code where everything passed in and passed out of methods is just a dictionary. It's lazy. I love dynamically typed languages, but I also like my code to be clear and logical. Shoving everything into a dictionary can be a convenience that hides meaning. – jaydel Aug 07 '16 at 14:51
5

I would recommend a class, as it is all sorts of information involved with a request. Were one to use a dictionary, I'd expect the data stored to be far more similar in nature. A guideline I tend to follow myself is that if I may want to loop over the entire set of key->value pairs and do something, I use a dictionary. Otherwise, the data apparently has far more structure than a basic key->value mapping, meaning a class would likely be a better alternative.

Hence, stick with the class.

Stigma
  • 1,686
  • 13
  • 27
  • 2
    I totally disagree. There's no reason to restrict the use of dictionaries to things to iterate over. They're for maintaining a mapping. – Katriel Oct 28 '10 at 17:09
  • 1
    Read a bit more closely, please. I said _may want to loop over_, not _will_. To me, using a dictionary implies there is a big functional similarity between the keys and values. For example, names with ages. Using a class would mean the various 'keys' have values with vastly different meanings. For example, take a class PErson. Here, 'name' would be a string, and 'friends' may be a list, or a dictionary, or some other suitable object. You wouldn't loop over all these attributes as part of normal usage of this class. – Stigma Oct 28 '10 at 21:53
  • 1
    I think the distinction between classes and dictionaries is made indistinct in Python by the fact that the former are implemented using the latter ('slots' not withstanding). I know this confused me a bit when I was first learning the language (along with the fact that classes were objects and therefore instances of some mysterious metaclasses). – martineau Oct 29 '10 at 17:53
4

If all that you want to achive is syntax candy like obj.bla = 5 instead of obj['bla'] = 5, especially if you have to repeat that a lot, you maybe want to use some plain container class as in martineaus suggestion. Nevertheless, the code there is quite bloated and unnecessarily slow. You can keep it simple like that:

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

Another reason to switch to namedtuples or a class with __slots__ could be memory usage. Dicts require significantly more memory than list types, so this could be a point to think about.

Anyways, in your specific case, there doesn't seem to be any motivation to switch away from your current implementation. You don't seem to maintain millions of these objects, so no list-derived-types required. And it's actually containing some functional logic within the __init__, so you also shouldn't got with AttrDict.

Michael
  • 7,316
  • 1
  • 37
  • 63
  • 1
    [`types.SimpleNamespace`](https://docs.python.org/3/library/types.html#types.SimpleNamespace) (available since Python 3.3) is an alternative to the custom AttrDict. – Cristian Ciupitu Aug 14 '20 at 18:06
4

It may be possible to have your cake and eat it, too. In other words you can create something that provides the functionality of both a class and dictionary instance. See the ActiveState's Dɪᴄᴛɪᴏɴᴀʀʏ ᴡɪᴛʜ ᴀᴛᴛʀɪʙᴜᴛᴇ-sᴛʏʟᴇ ᴀᴄᴄᴇss recipe and comments on ways of doing that.

If you decide to use a regular class rather than a subclass, I've found the Tʜᴇ sɪᴍᴘʟᴇ ʙᴜᴛ ʜᴀɴᴅʏ "ᴄᴏʟʟᴇᴄᴛᴏʀ ᴏғ ᴀ ʙᴜɴᴄʜ ᴏғ ɴᴀᴍᴇᴅ sᴛᴜғғ" ᴄʟᴀss recipe (by Alex Martelli) to be very flexible and useful for the sort of thing it looks like you're doing (i.e. create a relative simple aggregator of information). Since it's a class you can easily extend its functionality further by adding methods.

Lastly it should be noted that the names of class members must be legal Python identifiers, but dictionary keys do not—so a dictionary would provide greater freedom in that regard because keys can be anything hashable (even something that's not a string).

Update

A class object (which doesn't have a __dict__) subclass named SimpleNamespace (which does have one) was added to the types module Python 3.3, and is yet another alternative.

martineau
  • 119,623
  • 25
  • 170
  • 301
1

If the data, I mean set of fields, is not to be changed or extended in the future i would choose a class for representation such data. Why?

  1. It's a little more clean and readable.
  2. It's faster in terms of using it, which is much more important than creating it, which happens generally only once.

Even faster seems using just class as container for fields not object of the class.

extending alexpinho98 example:

import timeit

class Foo(object):

    def __init__(self):

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

class FooClass:
        foo1 = 'test'
        foo2 = 'test'
        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

dict = create_dict()
def testDict():
    a = dict['foo1']
    b = dict['foo2']
    c = dict['foo3']

dict_obj = Foo()
def testObjClass():
    a = dict_obj.foo1
    b = dict_obj.foo2
    c = dict_obj.foo3

def testClass():
    a = FooClass.foo1
    b = FooClass.foo2
    c = FooClass.foo3


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 ('=== Testing usage 1 ===')
print ('Using dict  : ' + str(tmit('testDict()', 'from __main__ import testDict')))
print ('Using object: ' + str(tmit('testObjClass()', 'from __main__ import testObjClass')))
print ('Using class : ' + str(tmit('FooClass()', 'from __main__ import FooClass')))

Results are:

Creating...

Dict: 0.185864600000059
Class: 0.30627199999980803
Class with slots: 0.2572166999998444
=== Testing usage 1 ===
Using dict  : 0.16507520000050135
Using object: 0.1266871000007086
Using class : 0.06327920000148879
Zbyszek
  • 647
  • 2
  • 8
  • 21
-3
class ClassWithSlotBase:
    __slots__ = ('a', 'b',)

def __init__(self):
    self.a: str = "test"
    self.b: float = 0.0


def test_type_hint(_b: float) -> None:
    print(_b)


class_tmp = ClassWithSlotBase()

test_type_hint(class_tmp.a)

I recommend a class. If you use a class, you can get type hint as shown. And Class support auto complete when class is argument of function.

enter image description here