32

Quite often, I find myself wanting a simple, "dump" object in Python which behaves like a JavaScript object (ie, its members can be accessed either with .member or with ['member']).

Usually I'll just stick this at the top of the .py:

class DumbObject(dict):
    def __getattr__(self, attr):
        return self[attr]
    def __stattr__(self, attr, value):
        self[attr] = value

But that's kind of lame, and there is at least one bug with that implementation (although I can't remember what it is).

So, is there something similar in the standard library?

And, for the record, simply instanciating object doesn't work:

>>> obj = object()
>>> obj.airspeed = 42
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: 'object' object has no attribute 'airspeed'

Edit: (dang, should have seen this one coming)… Don't worry! I'm not trying to write JavaScript in Python. The place I most often find I want this is while I'm still experimenting: I have a collection of "stuff" that doesn't quite feel right to put in a dictionary, but also doesn't feel right to have its own class.

David Wolever
  • 148,955
  • 89
  • 346
  • 502
  • As I said below, you're trying to code around incorrect instincts brought from another language. In Python, we write classes even when we're "experimenting". Try -- stop writing DumbObject, actually write "class Foo" and see that it does not bite. – moshez Apr 14 '10 at 21:36
  • And, as I said below… That is an interesting idea - simply using, eg, class Foo: airspeed = 42 - I hadn't considered that. However, it does have the problem that it doesn't behave like a dictionary (yes, there is __dict__…), which makes experimentation more difficult. – David Wolever Apr 14 '10 at 22:27
  • 1
    Looks like something we all want to do with Python :) http://stackoverflow.com/questions/2466191/set-attributes-from-dictionary-in-python – OscarRyz Apr 15 '10 at 00:45
  • I'm sticking my neck out here but have you considered the use of a key/value store like Redis? It excels as a collection of "stuff", as you put it. The Python client is absurdly easy to use. – jathanism Apr 15 '10 at 00:59

10 Answers10

21

In Python 3.3+ you can use SimpleNamespace, which does exactly what you're looking for:

from types import SimpleNamespace
obj = SimpleNamespace()
obj.airspeed = 42

https://docs.python.org/3.4/library/types.html#types.SimpleNamespace

bscan
  • 2,816
  • 1
  • 16
  • 16
14

You can try with attrdict:

class attrdict(dict):
    def __init__(self, *args, **kwargs):
        dict.__init__(self, *args, **kwargs)
        self.__dict__ = self

a = attrdict(x=1, y=2)
print a.x, a.y
print a['x'], a['y']

b = attrdict()
b.x, b.y  = 1, 2
print b.x, b.y
print b['x'], b['y']
ars
  • 120,335
  • 23
  • 147
  • 134
  • Should complete your example with, for example, `print b['x']`. To show that it supports both attribute access methods. – Gary Kerr Apr 14 '10 at 22:55
  • Good point, PreludeAndFugue. I updated the code. Thanks. :) – ars Apr 14 '10 at 22:57
  • 1
    Changing `__dict__`?! That's disgusting! But, at the same time, intriguing… – David Wolever Apr 15 '10 at 20:51
  • 1
    WARNING! This works, but can cause huge memory leaks due to the circular reference. Putting it in a loop fills my 8 GB of RAM in mere seconds. Don't use this for code that actually matters! – fletom Mar 23 '12 at 00:24
  • 1
    @fletom let me guess... you've decided to create a gazillion objects using `range` in python 2... the memory is not leaking from the attrdicts, it's because `range` actually creates a list with the numbers in python 2! The garbage collector handles the circular references just fine :) I've just created and discarded 10**9 attrdicts and memory usage didn't even change (did not run till the end, but enough time to see it wasn't leaking). Use `xrange` and you'll be fine ;) – El Ninja Trepador Sep 07 '16 at 11:26
10

There is no "standard library" with that kind of object, but on ActiveState there is a quite well-known recipe from Alex Martelli, called "bunch".

Note: there's also a package available on pypi called bunch and that should do about the same thing, but I do not know anything about its implementation and quality.

rob
  • 36,896
  • 2
  • 55
  • 65
8

You may be interested in collections.namedtuple

satoru
  • 31,822
  • 31
  • 91
  • 141
7

If I understand you correctly, you want an object where you can just dump attributes. If I am correct, all you would have to do is create an empty class. For example:

>>> class DumpObject: pass
... 
>>>#example of usage:
...
>>> a = DumpObject()
>>> a.airspeed = 420
>>> print a.airspeed
420

That's it

2

This method works with nested dictionaries and is quite concise.

Firstly, define an empty class. Instances of this class can be set any attributes.

class holder:
    pass

Next, just use this function.

def dotted(object):

    if isinstance(object, dict):
        parent = holder()

        for key in object:

            child = object.get(key)
            setattr(parent, key, dotted(child))

        return parent

    return object

Example usage.

users = dotted(
{
    'john': {
        'fullname': 'John Doe'
        'age': 24,
        'hobbies': [
            'programming',
            'jogging'
        ]
    }
}

users.john.fullname # 'John Doe'
users.john.age      # 24
users.john.hobbies  # ['programming', 'jogging']

users.john.age = 25
users.john.age      # 25
teacat
  • 21
  • 2
1

Well, as far as i can tell (correct me if i wrong) every example here doesn't convert nested dictionaries to a new type. So i made this example:

class DotDictionary(dict):
 
    def __init__(self, init_value):
        if not isinstance(init_value, dict):
            raise TypeError('Expected dict, got {}'.format(type(init_value)))
 
        self._dict_to_self(init_value)
 
    def __getattr__(self, name):
        return self[name]
 
    def __setattr__(self, name, val):
        self[name] = val
 
    def __delattr__(self, name):
        del self[name]
 
    def _dict_to_self(self, dct):
        for key, value in dct.items():
            if isinstance(value, dict):
                value = DotDictionary(value)
            if isinstance(value, (tuple, list)):
                for i, _ in enumerate(value):
                    if isinstance(value[i], dict):
                        value[i] = DotDictionary(value[i])
 
            self[key] = value

ideone

I'm not 100% sure that it works absolutely correct, so no warranty here.

Also maybe it's not pythonic-way so i'm ready to take advices.

InSync
  • 4,851
  • 4
  • 8
  • 30
Jack White
  • 11
  • 1
0

I'm not a big fan of that, because you get into the "what if you define 'keys'":

foo = DumbObject()
foo.moshe = 'here'
foo.keys # --> gives a callable, if you'll call it you'll get ['moshe']
foo.keys = 'front-door'
foo.keys # --> oh look, now it's a string 'front-door'

Is "keys" supposed to be a defined method on your object, or is that just happenstance? If it is, then you better define DumbObject as

class DumbObject(object):
    def __init__(self):
        self._dict = {}
    def __getitem__(self, key):
        return self._dict[key]
    __getattr__ = __getitem__
    # Ditto for set
def asDictionary(do):
    return do._dict

But why are you doing it? Are you sure that this is the right answer? Perhaps you should be using the right type of object?

moshez
  • 36,202
  • 1
  • 22
  • 14
  • Ah, I hadn't considered that. In that case, there are at least two bugs with the implementation I've used in the past :P – David Wolever Apr 14 '10 at 20:48
  • I often like it to experiment with - for data that doesn't really feel "right" in a dictionary, but formalizing it into a class feel overkill. – David Wolever Apr 14 '10 at 20:53
  • 3
    I think it's your feelings which are the problem -- "formalizing it into a class" is probably not an overkill. It feels like an overkill in languages where classes aren't, at a minimum, a mere "class Foo(object): pass" :-D – moshez Apr 14 '10 at 21:20
  • That is an interesting idea - simply using, eg, `class Foo: airspeed = 42` - I hadn't considered that. However, it does have the problem that it doesn't behave like a dictionary (yes, there is `__dict__`…), which makes experimentation more difficult. – David Wolever Apr 14 '10 at 22:27
0
DumbObject = lambda: "function is a first-class object And this sentence isn't necessary :), you could simply put None or Pass"

DumbObject.name = "Dumb"
DumbObject.speak = lambda word: print(word)
DumbObject.tell_name =  lambda : print(f"I am {DumbObject.name} and have no self")

DumbObject.speak("I speak not")
DumbObject.tell_name()
Rezan Moh
  • 336
  • 2
  • 7
0

This implementation allows you to arbitrarily nest dicts and SimpleNamespaces, and is compatible with json.dumps, since it inherits from dict.

from types import SimpleNamespace
import json


class DictNamespace(dict):

    def __getattr__(self, key):
        return self[key]

    def __setattr__(self, key, value):
        self[key] = self.construct_namespace(value)

    def __delattr__(self, key):
        del self[key]

    @staticmethod
    def construct_namespace(maybe_dict):
        if isinstance(maybe_dict, dict):
            for key, value in maybe_dict.items():
                if isinstance(value, dict):
                    maybe_dict[key] = DictNamespace(**value)
                elif isinstance(value, SimpleNamespace):
                    maybe_dict[key] = DictNamespace(**value.__dict__)
                else:
                    maybe_dict[key] = value
        return maybe_dict

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        DictNamespace.construct_namespace(self)


thing = DictNamespace(
    one=1, 
    two=dict(three=3), 
    five=DictNamespace(six=6), 
    eight=SimpleNamespace(nine=9)
)

thing.eleven = 11
print(thing.one)
print(thing.two)
print(thing.two.three)
print(thing.five)
print(thing.five.six)
print(thing.eight)
print(thing.eight.nine)
print(thing["eight"]["nine"])
print(thing["eight"].nine)
print(thing.eleven)
del thing.eleven
assert thing.get("eleven", None) is None
print(json.dumps(thing))
DragonBobZ
  • 2,194
  • 18
  • 31