which "dictionary with attribute-style access"?

A

Andreas Balogh

Hello,

googling I found several ways of implementing a "dictionary with
attribute-style access".

1. ActiveState cookbook: http://code.activestate.com/recipes/473786/

2. ActiveState cookbook:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/361668

3. web2py codebase: Storage(dict)

I enclosed the three implementations below.

My question to the Python specialists: which one is the most correct?
Are there restrictions with regards to pickling or copy()?
Which one should I choose?

Regards, Andreas

--
Andreas Balogh
baloand (at) gmail.com

-----------------------------------------------------------------------
class AttrDict(dict):
""" comments removed """
"""A dictionary with attribute-style access. It maps attribute
access to the real dictionary. """
def __init__(self, init={}):
dict.__init__(self, init)

def __getstate__(self):
return self.__dict__.items()

def __setstate__(self, items):
for key, val in items:
self.__dict__[key] = val

def __repr__(self):
return "%s(%s)" % (self.__class__.__name__, dict.__repr__(self))

def __setitem__(self, key, value):
return super(AttrDict, self).__setitem__(key, value)

def __getitem__(self, name):
return super(AttrDict, self).__getitem__(name)

def __delitem__(self, name):
return super(AttrDict, self).__delitem__(name)

__getattr__ = __getitem__
__setattr__ = __setitem__

def copy(self):
ch = AttrDict(self)
return ch
-----------------------------------------------------------------------
class attrdict(dict):
""" comments removed """
def __init__(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs)
self.__dict__ = self
-----------------------------------------------------------------------
class Storage(dict):
""" comments removed """
def __getattr__(self, key):
try:
return self[key]
except KeyError, k:
return None

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

def __delattr__(self, key):
try:
del self[key]
except KeyError, k:
raise AttributeError, k

def __repr__(self):
return '<Storage ' + dict.__repr__(self) + '>'

def __getstate__(self):
return dict(self)

def __setstate__(self, value):
for (k, v) in value.items():
self[k] = v
 
G

Gabriel Genellina

googling I found several ways of implementing a "dictionary with
attribute-style access".
1. ActiveState cookbook: http://code.activestate.com/recipes/473786/
2. ActiveState cookbook:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/361668
3. web2py codebase: Storage(dict)
I enclosed the three implementations below.
My question to the Python specialists: which one is the most correct?
Are there restrictions with regards to pickling or copy()?
Which one should I choose?
-----------------------------------------------------------------------
class AttrDict(dict):
""" comments removed """
"""A dictionary with attribute-style access. It maps attribute
access to the real dictionary. """
def __init__(self, init={}):
dict.__init__(self, init)
def __getstate__(self):
return self.__dict__.items()
def __setstate__(self, items):
for key, val in items:
self.__dict__[key] = val
def __repr__(self):
return "%s(%s)" % (self.__class__.__name__, dict.__repr__(self))
def __setitem__(self, key, value):
return super(AttrDict, self).__setitem__(key, value)
def __getitem__(self, name):
return super(AttrDict, self).__getitem__(name)
def __delitem__(self, name):
return super(AttrDict, self).__delitem__(name)
__getattr__ = __getitem__
__setattr__ = __setitem__
def copy(self):
ch = AttrDict(self)
return ch

__init__, __setitem__, __getitem__, __delitem__ are redundant if they just
call the inherited behaviour.
__getstate__/setstate are redundant too, because the base class (dict)
already knows how to serialize itself, and AttrDict is not adding
additional attributes. The remaining methods are __repr__, __getattr__,
__setattr__ (why not __delattr__?) and copy, but still don't work very
well (e.g. __getattr__ should raise AttributeError, not KeyError, for
unknown attributes).
-----------------------------------------------------------------------
class attrdict(dict):
""" comments removed """
def __init__(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs)
self.__dict__ = self

I like this -- I'd add repr/copy/fromkeys so they are aware of the
subclassing.
-----------------------------------------------------------------------
class Storage(dict):
""" comments removed """
def __getattr__(self, key):
try:
return self[key]
except KeyError, k:
return None
def __setattr__(self, key, value):
self[key] = value
def __delattr__(self, key):
try:
del self[key]
except KeyError, k:
raise AttributeError, k
def __repr__(self):
return '<Storage ' + dict.__repr__(self) + '>'
def __getstate__(self):
return dict(self)
def __setstate__(self, value):
for (k, v) in value.items():
self[k] = v

Not so bad, but some comments above still apply.
Try to build the best implementation from all those pieces. Below are some
tests I'd expect a decent implementation should pass:

assert Subclass() == {}
d1 = {1:2, '3':4, 'name': 'value', '__getattr__': 5, '__getitem__': 6}
d2 = Subclass(d1)
assert d1 == d2

assert d2.copy() == d2
assert isinstance(d2.copy(), type(d2))
assert eval(repr(d2)) == d2
assert isinstance(eval(repr(d2)), type(d2))

d3 = Subclass.fromkeys([1,2,3])
assert isinstance(d3, Subclass)
assert d3 == {1:None, 2:None, 3:None}

assert d2[1] == 2
assert d2['name'] == d2.name == 'value'
assert d2.__getattr__ == 5

assert not hasattr(d2, 'xyz')
assert 'xyz' not in d2
d2.xyz = 123
assert d2.xyz == d2['xyz'] == 123
assert 'xyz' in d2

d2['xyz'] = 456
assert d2['xyz'] == d2.xyz == 456
assert hasattr(d2, 'xyz')

d2['abc'] = 789
assert d2.abc == d2['abc'] == 789

d2.abc = 123
assert d2.abc == d2['abc'] == 123

del d2.abc
assert not hasattr(d2, 'abc')
assert 'abc' not in d2
del d2['xyz']
assert not hasattr(d2, 'xyz')
assert 'xyz' not in d2

d4 = loads(dumps(d2))
assert d2 == d4
assert isinstance(d4, type(d2))

(plus all the expected behavior from dict itself: clear, get, update...)
 
A

Aahz

My question to the Python specialists: which one is the most correct?
Are there restrictions with regards to pickling or copy()?
Which one should I choose?

What's your goal? I'd probably do the dirt simple myself:

class AttrDict(dict):
def __getattr__(self, attr):
if attr in self:
return self[attr]
else:
raise AttributeError

def __setattr__(self, attr, value):
self[attr] = value

d = AttrDict()
d.foo = 'bar'
print d.foo
--
Aahz ([email protected]) <*> http://www.pythoncraft.com/

"To me vi is Zen. To use vi is to practice zen. Every command is a
koan. Profound to the user, unintelligible to the uninitiated. You
discover truth everytime you use it." (e-mail address removed)
 
T

Terry Reedy

Aahz said:
My question to the Python specialists: which one is the most correct?
Are there restrictions with regards to pickling or copy()?
Which one should I choose?

What's your goal? I'd probably do the dirt simple myself:

class AttrDict(dict):
def __getattr__(self, attr):
if attr in self:
return self[attr]
else:
raise AttributeError

Why the double lookup? Harking to another thread on using exceptions,

try:
return self[attr]
except KeyError:
raise AttributeError(attr)
def __setattr__(self, attr, value):
self[attr] = value

d = AttrDict()
d.foo = 'bar'
print d.foo
 
A

Aahz

Aahz said:
My question to the Python specialists: which one is the most correct?
Are there restrictions with regards to pickling or copy()?
Which one should I choose?

What's your goal? I'd probably do the dirt simple myself:

class AttrDict(dict):
def __getattr__(self, attr):
if attr in self:
return self[attr]
else:
raise AttributeError

Why the double lookup? Harking to another thread on using exceptions,

try:
return self[attr]
except KeyError:
raise AttributeError(attr)

For this purpose, it's almost entirely a stylistic difference; I happen
to prefer using the test, but the other way is fine, too.
--
Aahz ([email protected]) <*> http://www.pythoncraft.com/

"To me vi is Zen. To use vi is to practice zen. Every command is a
koan. Profound to the user, unintelligible to the uninitiated. You
discover truth everytime you use it." (e-mail address removed)
 
A

Andreas Balogh

Gabriel, thanks for your hint. I've managed to create an implementation of an AttrDict
passing Gabriels tests.

Any more comments about the pythonicness of this implementation?

class AttrDict(dict):
"""A dict whose items can also be accessed as member variables."""
def __init__(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs)
self.__dict__ = self

def copy(self):
return AttrDict(self)

def __repr__(self):
return 'AttrDict(' + dict.__repr__(self) + ')'

@classmethod
def fromkeys(self, seq, value = None):
return AttrDict(dict.fromkeys(seq, value))
 
G

Gabriel Genellina

Gabriel, thanks for your hint. I've managed to create an implementation
of an AttrDict passing Gabriels tests.

Any more comments about the pythonicness of this implementation?

class AttrDict(dict):
"""A dict whose items can also be accessed as member variables."""
def __init__(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs)
self.__dict__ = self

def copy(self):
return AttrDict(self)

def __repr__(self):
return 'AttrDict(' + dict.__repr__(self) + ')'

@classmethod
def fromkeys(self, seq, value = None):
return AttrDict(dict.fromkeys(seq, value))

Looks fine as long as nobody uses an existing method name as a dictionary
key:

py> d = AttrDict({'name':'value'})
py> d.items()
[('name', 'value')]
py> d = AttrDict({'items': [1,2,3]})
py> d.items()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'list' object is not callable

(I should have included a test case for this issue too).
Of course, if this doesn't matter in your application, go ahead and use
it. Just be aware of the problem.
 
B

baloan

class AttrDict(dict):
     """A dict whose items can also be accessed as member variables."""
     def __init__(self, *args, **kwargs):
         dict.__init__(self, *args, **kwargs)
         self.__dict__ = self
     def copy(self):
         return AttrDict(self)
     def __repr__(self):
         return 'AttrDict(' + dict.__repr__(self) + ')'
     @classmethod
     def fromkeys(self, seq, value = None):
         return AttrDict(dict.fromkeys(seq, value))

Looks fine as long as nobody uses an existing method name as adictionary 
key:

py> d = AttrDict({'name':'value'})
py> d.items()
[('name', 'value')]
py> d = AttrDict({'items': [1,2,3]})
py> d.items()
Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
TypeError: 'list' object is not callable

(I should have included a test case for this issue too).
Of course, if this doesn't matter in your application, go ahead and use  
it. Just be aware of the problem.

I see two ways to avoid collisions with existing method names:

1. (easy, reduced functionality) override __setattr__ and __init__,
test for keys matching existing method names, throw an exception if
exists (KeyError).
2. (complex, emulates dict) override __setattr__ and __init__, test
for keys matching existing method names, and store those keys in a
shadow dict. More problems arise when thinking about how to choose
access between dict method names and item keys.
 
A

Andreas Balogh

baloan said:
class AttrDict(dict):
"""A dict whose items can also be accessed as member variables."""
def __init__(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs)
self.__dict__ = self
def copy(self):
return AttrDict(self)
def __repr__(self):
return 'AttrDict(' + dict.__repr__(self) + ')'
@classmethod
def fromkeys(self, seq, value = None):
return AttrDict(dict.fromkeys(seq, value))
Looks fine as long as nobody uses an existing method name as adictionary
key:

py> d = AttrDict({'name':'value'})
py> d.items()
[('name', 'value')]
py> d = AttrDict({'items': [1,2,3]})
py> d.items()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'list' object is not callable

(I should have included a test case for this issue too).
Of course, if this doesn't matter in your application, go ahead and use
it. Just be aware of the problem.

I see two ways to avoid collisions with existing method names:

1. (easy, reduced functionality) override __setattr__ and __init__,
test for keys matching existing method names, throw an exception if
exists (KeyError).
2. (complex, emulates dict) override __setattr__ and __init__, test
for keys matching existing method names, and store those keys in a
shadow dict. More problems arise when thinking about how to choose
access between dict method names and item keys.

That was indeed my last post. It seems for this thread Google does not sort correctly
descending "creation time fo the last post". This thread was not visible as "new" when I
posted...

Regards, Andreas
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,581
Members
45,056
Latest member
GlycogenSupporthealth

Latest Threads

Top