24

I've started to use constructs like these:

class DictObj(object):
    def __init__(self):
        self.d = {}
    def __getattr__(self, m):
        return self.d.get(m, None)
    def __setattr__(self, m, v):
        super.__setattr__(self, m, v)

Update: based on this thread, I've revised the DictObj implementation to:

class dotdict(dict):
    def __getattr__(self, attr):
        return self.get(attr, None)
    __setattr__= dict.__setitem__
    __delattr__= dict.__delitem__

class AutoEnum(object):
    def __init__(self):
        self.counter = 0
        self.d = {}
    def __getattr__(self, c):
        if c not in self.d:
            self.d[c] = self.counter
            self.counter += 1        
        return self.d[c]

where DictObj is a dictionary that can be accessed via dot notation:

d = DictObj()
d.something = 'one'

I find it more aesthetically pleasing than d['something']. Note that accessing an undefined key returns None instead of raising an exception, which is also nice.

Update: Smashery makes a good point, which mhawke expands on for an easier solution. I'm wondering if there are any undesirable side effects of using dict instead of defining a new dictionary; if not, I like mhawke's solution a lot.

AutoEnum is an auto-incrementing Enum, used like this:

CMD = AutoEnum()

cmds = {
    "peek":  CMD.PEEK,
    "look":  CMD.PEEK,
    "help":  CMD.HELP,
    "poke":  CMD.POKE,
    "modify": CMD.POKE,
}

Both are working well for me, but I'm feeling unpythonic about them.

Are these in fact bad constructs?

Parand
  • 102,950
  • 48
  • 151
  • 186
  • Very nice! class dotdict is minimal and elegant. I think I will use it. About the "AutoEnum", however...my problem is that the "value" for each "key" might be different on each run, so you can't use it for values that are stored in a database or anything ( well, I suppose you could keep the values the same, if you are careful never to change the order of initial access...but the values are not explicit ) – Nick Perkins Aug 20 '11 at 14:17
  • 3
    Wait!, there's a problem with dotdict: it doesn't work if you try to use a key that is the name of a dict method, like any of these: "items", "keys", "get", "clear", "copy", etc....that's a bit of a problem. Is there any way around this? Is there any way to "have your cake and eat it too" ? ( to be able to call dict methods, but also to be able to use keys like "items" and "clear" without changing syntax? ). – Nick Perkins Aug 20 '11 at 14:40

12 Answers12

23

Your DictObj example is actually quite common. Object-style dot-notation access can be a win if you are dealing with ‘things that resemble objects’, ie. they have fixed property names containing only characters valid in Python identifiers. Stuff like database rows or form submissions can be usefully stored in this kind of object, making code a little more readable without the excess of ['item access'].

The implementation is a bit limited - you don't get the nice constructor syntax of dict, len(), comparisons, 'in', iteration or nice reprs. You can of course implement those things yourself, but in the new-style-classes world you can get them for free by simply subclassing dict:

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

To get the default-to-None behaviour, simply subclass Python 2.5's collections.defaultdict class instead of dict.

eatonphil
  • 13,115
  • 27
  • 76
  • 133
bobince
  • 528,062
  • 107
  • 651
  • 834
  • I tried this with defaultdict, but it's still not returning None for underfined:
    >>> d = AttrDict()
    >>> d.me
    Traceback (most recent call last):
      File "", line 1, in 
    KeyError: 'me'
    
    – Parand Oct 23 '08 at 22:42
  • I settled on a slightly modified version of this, now included in the updated question above. – Parand Oct 24 '08 at 13:18
  • defaultdict worked for me - did you remember to include the factory function in the constructor? ie. d= defaultdict(lambda: None). If you forget that bit defaultdict just works like a normal dict. You could also override __init__ in AttrDict to do automatically, perhaps. – bobince Oct 25 '08 at 00:26
12

With regards to the DictObj, would the following work for you? A blank class will allow you to arbitrarily add to or replace stuff in a container object.

class Container(object):
    pass

>>> myContainer = Container()
>>> myContainer.spam = "in a can"
>>> myContainer.eggs = "in a shell"

If you want to not throw an AttributeError when there is no attribute, what do you think about the following? Personally, I'd prefer to use a dict for clarity, or to use a try/except clause.

class QuietContainer(object):
    def __getattr__(self, attribute):
        try:
            return object.__getattr__(self,attribute)
        except AttributeError:
            return None

>>> cont = QuietContainer()
>>> print cont.me
None

Right?

hlzr
  • 378
  • 1
  • 7
  • I agree creating a class seems like a much cleaner way to handle what he is attempting. – monkut Oct 22 '08 at 01:54
  • The key difference is I don't want an exception thrown when the key hasn't been previously defined. – Parand Oct 22 '08 at 03:17
  • What part of Kevin's example throws an exception? – Robert Rossney Oct 22 '08 at 05:51
  • @Robert: The part where you try "print myContainer.blah" raises AttributeError. In Parand's DictObj, "d.blah" would return None, no exception. – mhawke Oct 22 '08 at 06:32
  • What about defining a special __getattr__ method? It could just catch the exception and return none. Lemme fix my code to include that. – hlzr Oct 24 '08 at 23:39
8

This is a simpler version of your DictObj class:

class DictObj(object):
    def __getattr__(self, attr):
        return self.__dict__.get(attr)

>>> d = DictObj()
>>> d.something = 'one'
>>> print d.something
one
>>> print d.somethingelse
None
>>> 
mhawke
  • 84,695
  • 9
  • 117
  • 138
  • 1
    Any possible side effects of using __dict__? If not, I like it. – Parand Oct 22 '08 at 08:24
  • 1
    If I recall, __dict__ isn't technically part of the python spec. That is, other VMs don't need to implement. I also recall that all current VMs (IronPython, Jython, PyPy) actually *do* use a __dict__ for object dictionaries. – Ryan Oct 25 '08 at 00:11
  • 1
    This is bad, I like the `dotdict` class from the question *much* better. The `dotdict` is an actual dictionary, so can be converted to JSON naturally, where as this object of yours, is not. – hasen Jan 25 '12 at 17:28
3

As far as I know, Python classes use dictionaries to store their attributes anyway (that's hidden from the programmer), so it looks to me that what you've done there is effectively emulate a Python class... using a python class.

Smashery
  • 57,848
  • 30
  • 97
  • 128
3

It's not "wrong" to do this, and it can be nicer if your dictionaries have a strong possibility of turning into objects at some point, but be wary of the reasons for having bracket access in the first place:

  1. Dot access can't use keywords as keys.
  2. Dot access has to use Python-identifier-valid characters in the keys.
  3. Dictionaries can hold any hashable element -- not just strings.

Also keep in mind you can always make your objects access like dictionaries if you decide to switch to objects later on.

For a case like this I would default to the "readability counts" mantra: presumably other Python programmers will be reading your code and they probably won't be expecting dictionary/object hybrids everywhere. If it's a good design decision for a particular situation, use it, but I wouldn't use it without necessity to do so.

cdleary
  • 69,512
  • 53
  • 163
  • 191
2

There's a symmetry between this and this answer:

class dotdict(dict):
    __getattr__= dict.__getitem__
    __setattr__= dict.__setitem__
    __delattr__= dict.__delitem__

The same interface, just implemented the other way round...

class container(object):
    __getitem__ = object.__getattribute__
    __setitem__ = object.__setattr__
    __delitem__ = object.__delattr__
Community
  • 1
  • 1
Sebastian
  • 1,839
  • 12
  • 16
2

The one major disadvantage of using something like your DictObj is you either have to limit allowable keys or you can't have methods on your DictObj such as .keys(), .values(), .items(), etc.

Ryan
  • 15,016
  • 6
  • 48
  • 50
2

Don't overlook Bunch.

It is a child of dictionary and can import YAML or JSON, or convert any existing dictionary to a Bunch and vice-versa. Once "bunchify"'d, a dictionary gains dot notations without losing any other dictionary methods.

Dannid
  • 1,507
  • 1
  • 20
  • 17
  • bunch is retired and the go-to replacement is now **munch** (https://pypi.python.org/pypi/munch). Use `Munch()` rather than `Bunch()` and `munchify` rather than `bunchify` – Dannid Mar 23 '17 at 12:18
1

I like dot notation a lot better than dictionary fields personally. The reason being that it makes autocompletion work a lot better.

Jason Baker
  • 192,085
  • 135
  • 376
  • 510
1

It's not bad if it serves your purpose. "Practicality beats purity".

I saw such approach elserwhere (eg. in Paver), so this can be considered common need (or desire).

zgoda
  • 12,775
  • 4
  • 37
  • 46
0

Because you ask for undesirable side-effects:

A disadvantage is that in visual editors like eclipse+pyDev, you will see many undefined variable errors on lines using the dot notation. Pydef will not be able to find such runtime "object" definitions. Whereas in the case of a normal dictionary, it knows that you are just getting a dictionary entry.

You would need to 1) ignore those errors and live with red crosses; 2) suppress those warnings on a line by line basis using #@UndefinedVariable or 3) disable undefined variable error entirely, causing you to miss real undefined variable definitions.

Andre Blum
  • 391
  • 3
  • 6
0

If you're looking for an alternative that handles nested dicts:

Recursively transform a dict to instances of the desired class

import json
from collections import namedtuple


class DictTransformer():
    @classmethod
    def constantize(self, d):
        return self.transform(d, klass=namedtuple, klassname='namedtuple')

    @classmethod
    def transform(self, d, klass, klassname):
        return self._from_json(self._to_json(d), klass=klass, klassname=klassname)

    @classmethod
    def _to_json(self, d, access_method='__dict__'):
        return json.dumps(d, default=lambda o: getattr(o, access_method, str(o)))

    @classmethod
    def _from_json(self, jsonstr, klass, klassname):
        return json.loads(jsonstr, object_hook=lambda d: klass(klassname, d.keys())(*d.values()))

Ex:

constants = {
  'A': {
    'B': {
      'C': 'D'
    }
  }
}
CONSTANTS = DictTransformer.transform(d, klass=namedtuple, klassname='namedtuple')
CONSTANTS.A.B.C == 'D'

Pros:

  • handles nested dicts
  • can potentially generate other classes
  • namedtuples provide immutability for constants

Cons:

  • may not respond to .keys and .values if those are not provided on your klass (though you can sometimes mimic with ._fields and list(A.B.C))

Thoughts?

h/t to @hlzr for the original class idea

fkotsian
  • 194
  • 1
  • 6