class attrdict

  • Thread starter Hallvard B Furuseth
  • Start date
H

Hallvard B Furuseth

Does this class need anything more?
Is there any risk of a lookup loop?
Seems to work...

class attrdict(dict):
"""Dict where d['foo'] also can be accessed as d.foo"""
def __init__(self, *args, **kwargs):
self.__dict__ = self
dict.__init__(self, *args, **kwargs)
def __repr__(self):
return dict.__repr__(self).join(("attrdict(", ")"))
a = attrdict([(1,2)], a=3, b=4)
a attrdict({'a': 3, 1: 2, 'b': 4})
a = attrdict([(1,2)], b=3, c=4)
a attrdict({1: 2, 'c': 4, 'b': 3})
a.b 3
a.d = 5
a['d'] 5
a.e
Traceback (most recent call last):
File said:
a.__getattr__ = 'xyzzy'
a.__getattribute__ = 'xyzzy'
a.__setattr__ = 'xyzzy'
a.__delattr__ = 'xyzzy'
a.c 4
a[1] 2
del a.c
 
J

James Stroud

Hallvard said:
Does this class need anything more?
Is there any risk of a lookup loop?
Seems to work...

class attrdict(dict):
"""Dict where d['foo'] also can be accessed as d.foo"""
def __init__(self, *args, **kwargs):
self.__dict__ = self
dict.__init__(self, *args, **kwargs)
def __repr__(self):
return dict.__repr__(self).join(("attrdict(", ")"))

Strangely enough, this seems okay since an instance of a dict subclass
object has an empty __dict__ attribute anyway and so you won't be
unwittingly destroying some behavior.

James
 
A

Alex Martelli

Hallvard B Furuseth said:
Does this class need anything more?
Is there any risk of a lookup loop?
Seems to work...

class attrdict(dict):
"""Dict where d['foo'] also can be accessed as d.foo"""
def __init__(self, *args, **kwargs):
self.__dict__ = self
dict.__init__(self, *args, **kwargs)
def __repr__(self):
return dict.__repr__(self).join(("attrdict(", ")"))

The problem is mostly that, given an instance a of attrdict, whether you
can call (e.g.) a.update(foo) depends on whether you ever set
a['update'], making the whole program extremely fragile -- a very high
price to pay for some modest amount of syntax sugar.


Alex
 
M

MonkeeSage

The problem is mostly that, given an instance a of attrdict, whether you
can call (e.g.) a.update(foo) depends on whether you ever set
a['update'], making the whole program extremely fragile -- a very high
price to pay for some modest amount of syntax sugar.

How about something like...

class attrdict(dict):
def __init__(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs)
for k, v in self.items():
dict.__setattr__(self, str(k), v)
def __setitem__(self, k, v):
dict.__setitem__(self, k, v)
dict.__setattr__(self, str(k), v)
__setattr__ = __setitem__

Regards,
Jordan
 
A

Alex Martelli

MonkeeSage said:
The problem is mostly that, given an instance a of attrdict, whether you
can call (e.g.) a.update(foo) depends on whether you ever set
a['update'], making the whole program extremely fragile -- a very high
price to pay for some modest amount of syntax sugar.

How about something like...

class attrdict(dict):
def __init__(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs)
for k, v in self.items():
dict.__setattr__(self, str(k), v)
def __setitem__(self, k, v):
dict.__setitem__(self, k, v)
dict.__setattr__(self, str(k), v)
__setattr__ = __setitem__

Same problem: after x=attrdict(), x.update(foo) will work for a while,
then suddenly stop working after some innocuous loop such as:
for bah in yech: x[bah] = 23
when one of the items in yech just happens to be the word 'update'
(similar issues with words such as 'get', 'pop', 'clear', etc, etc).

Miscegenation between attributes and items *inevitably* sucks.


Alex
 
A

Andrew Coffman

Could you do something like this?

class attrdict(dict):
def __getattr__(self, attr):
if self.has_key(attr):
return self[attr]
else:
message = "'attrdict' object has no attribute '%s'" % attr
raise AttributeError, message

If you have a dict item with the same name as a method in the class, you
won't be able to get to it using syntax sugar, though.

It doesn't seem that the syntax sugar saves you much typing anyway
(a.foo vs. a['foo']), but perhaps it seems nicer in some aesthetic sense.

- Andrew Coffman


Alex said:
The problem is mostly that, given an instance a of attrdict, whether you
can call (e.g.) a.update(foo) depends on whether you ever set
a['update'], making the whole program extremely fragile -- a very high
price to pay for some modest amount of syntax sugar.

How about something like...

class attrdict(dict):
def __init__(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs)
for k, v in self.items():
dict.__setattr__(self, str(k), v)
def __setitem__(self, k, v):
dict.__setitem__(self, k, v)
dict.__setattr__(self, str(k), v)
__setattr__ = __setitem__


Same problem: after x=attrdict(), x.update(foo) will work for a while,
then suddenly stop working after some innocuous loop such as:
for bah in yech: x[bah] = 23
when one of the items in yech just happens to be the word 'update'
(similar issues with words such as 'get', 'pop', 'clear', etc, etc).

Miscegenation between attributes and items *inevitably* sucks.


Alex
 
A

Alex Martelli

Andrew Coffman said:
Could you do something like this?

class attrdict(dict):
def __getattr__(self, attr):
if self.has_key(attr):
return self[attr]
else:
message = "'attrdict' object has no attribute '%s'" % attr
raise AttributeError, message

If you have a dict item with the same name as a method in the class, you
won't be able to get to it using syntax sugar, though.

Yes. In __setattr__, you can choose what to do in that case (it doesn't
seem to make sense to be able to *assign* to foo.get without being able
to *access* as foo.get whatever you've assigned).
It doesn't seem that the syntax sugar saves you much typing anyway
(a.foo vs. a['foo']), but perhaps it seems nicer in some aesthetic sense.

It would be nice, yes, weren't it for the inevitable irregularity, one
way or another, caused by the clash between attributes and items.

If, instead of subclassing dict, one wrapped it (and properly coded just
a few specialmethods, no ordinary ones), the overall effect could be
more regular (probably still with some limitation, since the dict does
need to be kept somewhere, and keys clashing with the special methods'
names would still have to be forbidden or otherwise specialcased).


Alex
 
M

MonkeeSage

It would be nice, yes, weren't it for the inevitable irregularity, one
way or another, caused by the clash between attributes and items.

In thinking about it, I think this might fall under 'we're all
consenting adults here'. I mean, you can shadow the dict constructor
from the toplevel if you're so inclined (or don't know any better).
But you could make it harder to accidentally shadow builtin
attributes:

from warnings import warn
class attrdict(dict):
__builtins__ = dir(dict)
def __init__(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs)
for k, v in self.items():
dict.__setattr__(self, str(k), v)
def __setitem__(self, k, v):
if k in self.__builtins__:
warn('shadowing builtin attribute: %s' % k,
RuntimeWarning, 2)
dict.__setitem__(self, k, v)
dict.__setattr__(self, str(k), v)
__setattr__ = __setitem__

a = attrdict([(1,2)], a=3, b=4)
a.update = 'test'

Regards,
Jordan
 
A

Alex Martelli

MonkeeSage said:
It would be nice, yes, weren't it for the inevitable irregularity, one
way or another, caused by the clash between attributes and items.

In thinking about it, I think this might fall under 'we're all
consenting adults here'. I mean, you can shadow the dict constructor
from the toplevel if you're so inclined (or don't know any better).
But you could make it harder to accidentally shadow builtin
attributes:

from warnings import warn
class attrdict(dict):
__builtins__ = dir(dict)
def __init__(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs)
for k, v in self.items():
dict.__setattr__(self, str(k), v)
def __setitem__(self, k, v):
if k in self.__builtins__:
warn('shadowing builtin attribute: %s' % k,
RuntimeWarning, 2)
dict.__setitem__(self, k, v)
dict.__setattr__(self, str(k), v)
__setattr__ = __setitem__

a = attrdict([(1,2)], a=3, b=4)
a.update = 'test'

Besides missing the warning in the __init__, this also has pretty weird
behavior whenever subject to a .pop, .update, .setdefault, del of either
item or attr, etc, etc: the attributes and items "get out of sync" (and,
catching each and every mutating-method to keep the separate dicts of
items and attrs in perfect sync is somewhat of a nightmare).

If the consenting adults in question WANT totally murky, irregular,
buggy behavior hiding under every corner -- why are they using Python in
the first place? It's so much easier to make such utter messes in other
languages, after all.

It's impossible to make something foolproof, because fools are so
ingenious; Python tends to keep them at bay by cultural forces (a strong
collective bias towards simplicity, regularity, correctness). The
neverending quest to confuse attributes and items (inevitably leading to
complications, irregularity, and bugs) is better pursued in other
languages -- just about ANY other language except Python.


Alex
 
M

MonkeeSage

Besides missing the warning in the __init__, this also has pretty weird
behavior whenever subject to a .pop, .update, .setdefault, del of either
item or attr, etc, etc: the attributes and items "get out of sync" (and,
catching each and every mutating-method to keep the separate dicts of
items and attrs in perfect sync is somewhat of a nightmare).

Good points. There are many complexities involved, and little actual
gain. Saving three or four chars isn't worth all the trouble. I hadn't
fully thought-out the situation.

Regards,
Jordan
 
A

Alex Martelli

MonkeeSage said:
Good points. There are many complexities involved, and little actual
gain. Saving three or four chars isn't worth all the trouble. I hadn't
fully thought-out the situation.

You make a good point. I do like being able to say foo.bar=baz rather
than foo['bar']=baz in certain cases -- not so much to save 3 chars, but
to avoid excessive punctuation; however, I don't really need this AND
all of dict's power at the same time, so, I don't inherit from dict:).


Alex
 
G

goodwolf

Hallvard B Furuseth said:
Does this class need anything more?
Is there any risk of a lookup loop?
Seems to work...
class attrdict(dict):
"""Dict where d['foo'] also can be accessed as d.foo"""
def __init__(self, *args, **kwargs):
self.__dict__ = self
dict.__init__(self, *args, **kwargs)
def __repr__(self):
return dict.__repr__(self).join(("attrdict(", ")"))

The problem is mostly that, given an instance a of attrdict, whether you
can call (e.g.) a.update(foo) depends on whether you ever set
a['update'], making the whole program extremely fragile -- a very high
price to pay for some modest amount of syntax sugar.

Alex

Then you will prefer something like this:

class Namespace(object):
def __init__(self, __ns={}, **kwargs):
if kwargs: __ns.update(kwargs)
self.__dict__ = __ns
 
G

goodwolf

Does this class need anything more?
Is there any risk of a lookup loop?
Seems to work...
class attrdict(dict):
"""Dict where d['foo'] also can be accessed as d.foo"""
def __init__(self, *args, **kwargs):
self.__dict__ = self
dict.__init__(self, *args, **kwargs)
def __repr__(self):
return dict.__repr__(self).join(("attrdict(", ")"))
The problem is mostly that, given an instance a of attrdict, whether you
can call (e.g.) a.update(foo) depends on whether you ever set
a['update'], making the whole program extremely fragile -- a very high
price to pay for some modest amount of syntax sugar.

Then you will prefer something like this:

class Namespace(object):
def __init__(self, __ns={}, **kwargs):
if kwargs: __ns.update(kwargs)
self.__dict__ = __ns

oops, there is an error (empty dict is created once).
Here corrected one:

class Namespace(object):
def __init__(self, __ns=None, **kwargs):
if __ns is None:
self.__dict__ = kwargs
else:
assert len(kwargs) == 0
self.__dict__ = __ns

If you are familiar with JS then you can simulate JS Object:

class JSLikeObject(object):
def __init__(self, __ns={}, **kwargs):
if kwargs: __ns.update(kwargs)
self.__dict__ = __ns
def __getitem__(self, name):
return getattr(self, name)
def __setitem__(self, name, value):
setattr(self, name, value)
def __delitem__(self, name):
delattr(self, name)
def __iter__(self):
return iter(self.__dict__)
def __contains__(self, name):
return hasattr(self, name)

but I don't sagest to use it in real life.
 
A

Alex Martelli

goodwolf said:
Then you will prefer something like this:

class Namespace(object):
def __init__(self, __ns={}, **kwargs):
if kwargs: __ns.update(kwargs)
self.__dict__ = __ns

I might, if it weren't for the redundant "if" and the horribly buggy
interference between separate instances -- which is why I wrote it,
almost six years ago and without the bugs, as
<http://aspn.activestate.com/ASPN/Python/Cookbook/Recipe/52308> .

Not much later, in the printed Cookbook, I also gave some other good
ways and explained (as I did in the current thread) why confusing
attributes and items, as proposed in most of this thread, is really a
bad idea.


Alex
 
G

goodwolf

class Namespace(object):
def __init__(self, __ns=None, **kwargs):
if __ns is None: #if no dictionary is given
self.__dict__ = kwargs #then use kwargs without copying
or creating new dict
else:
assert len(kwargs) == 0
self.__dict__ = __ns #else use dictionary without
copyng

#additional methods for JS like object (ONLY FOR DEMONSTRATION)
def __getitem__(self, name):
return getattr(self, name)
def __setitem__(self, name, value):
setattr(self, name, value)
def __delitem__(self, name):
delattr(self, name)
def __iter__(self):
return iter(self.__dict__)
def __contains__(self, name):
return hasattr(self, name)
 
H

Hallvard B Furuseth

Alex said:
You make a good point. I do like being able to say foo.bar=baz rather
than foo['bar']=baz in certain cases -- not so much to save 3 chars, but
to avoid excessive punctuation; however, I don't really need this AND
all of dict's power at the same time, so, I don't inherit from dict:).

Yes. Attribute syntax looks nicer, in particular one implements a sort
of private "variables collected in a dict" thing (e.g. SQL field names)
but still wants some dict functionality.

Another variant I thought of would be to prefix dict methods with '_'
(except those that already start with '__') and (if implemented as a
dict subtype) also override the original names with a "sorry, use
_<foo>" error method.

(Posting a bit sporatically currently, disappearing for a week again
now.)
 

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

Forum statistics

Threads
473,774
Messages
2,569,598
Members
45,160
Latest member
CollinStri
Top