
Show HN: Addict – a Python dict whos values can be get and set using attributes - mewwts
https://github.com/mewwts/addict
======
tyrion
I think you could implement it like this:

    
    
      from collections import defaultdict
      
      class D(defaultdict):
          
          def __init__(self):
              super(D, self).__init__(D)
          
          __getattr__ = defaultdict.__getitem__
          __setattr__ = defaultdict.__setitem__
          __repr__ = dict.__repr__

~~~
zb
Almost, he is also converting ordinary dicts that get added as items to his
own Dict class to enable you to keep using this syntax further down the tree,
so you'd need a small wrapper around __setitem__.

But yes, the idea of storing everything twice (once as an item and once as an
attribute) just so you can unconditionally add defaults in __getattr__ is…
bizarre and will break things horribly as soon as you accidentally do
something like:

    
    
        myDict['get'] = 'foo'

~~~
tyrion
> Almost, he is also converting ordinary dicts that get added as items to his
> own Dict class

You are right I dind't notice that before. But looking at the code it seems
that the only permitted values are dict and str?!

> But yes, the idea of storing everything twice (once as an item and once as
> an attribute) I don't get that, you don't store things twice and nothing
> breaks, at least with the example code I wrote above. Try:
    
    
      >>> a=D()
      >>> a.lol = 2
      >>> a['get'] = 'foo'
      >>> a
      {'get': 'foo', 'lol': 1}
      >>> vars(a)
      {}

~~~
zb
The only permitted _keys_ are str, and the rest are silently ignored. This is
another bug in his implementation (only the first line should be indented, but
both are).

Your code is fine - you're not storing everything twice, but he is (in
_set_both()).

~~~
mewwts
Oh thanks!

------
nvader
One of the best things about the Python ecosystem is the acceptance and
idealization of "Pythonic" solutions. While I acknowledge and praise the
authors effort in making this complete and correct, the aim itself is
misguided because it is grossly unpythonic. I reject it and hope it does not
see significant use.

~~~
adambard
The problem with "Pythonic" is that it's poorly-defined. There seems to be a
consensus on some things, and a wide disagreement on others.

You're welcome to your opinion, but I don't think it's productive to wield the
phrases "pythonic" and "unpythonic" as if they represent an objective truth.
If you have a problem with the library (and there are a few angles you could
take), say that instead of denouncing the library as blasphemy.

~~~
gbog
I have never seen anyone saying that pythonic refers to some objective truth.
It refers to subjective but real concepts as clarity, simplicity, explicit-
ness, etc. There is also one thing that is not really pythonic but still
important, it's reliability. Here addict is not reliable because it is unclear
what happens when you call "addict.get" or other dict built-in methods like
keys, items, etc. Something is reliable when it works equally for all values
in the value space. dict['x'] is reliable because it works the same even if
you replace 'x' by 'get' (or any other immutable value).

~~~
mewwts
Yes, I agree, some measure should be made to avoid overriding methods. Thanks!

------
ecma
This doesn't even come close to correctly implementing the MutableMapping
interface in a way that makes sense for the expected behaviour. The biggest
problem I see is that it's not at all clear how methods like __iter__,
key/value (and the view variants) and has_key should behave. Honestly, I'd
homebrew a nested defaultdict(defaultdict) every time because at least I know
what it'll do.

~~~
mewwts
Valid input, but a nested defaultdict won't let me recursively define items:
a.b.c.d.e = 2 as far as I can tell?

~~~
ecma
It does recursive access but not attribute access. The problem I see is that
attribute access is only part of the problem you're attempting to solve.
That's a nice feature but a pretty well known problem (check out the
self.__dict__ = self trick) with fairly clear behaviour mimicking the usual
dict.

The surprising bit that would stop me dropping this in any real code is that
as an end user, it's not immediately clear how the mapping interface should
apply to this class. It's all implementation defined but it's not actually
defined in the implementation. e.g., Does my Dict() have the key 'a.b.c'?
Nope, it only has 'a' and the other keys are managed through some magic.
Same.applies to iteration, it might be reasonable to assume that'd be a depth
first terminating node iterator but it would actually just iterate the top
level of the main Dict instance.

~~~
mewwts
I agree, the code is quite immature. However, your example is a bit off. If
you do my_dict.a.b.c = 2, your Dict will only have the key 'a', as it
generates nested Dicts. Hopefully you would not expect it to have 'a.b.c' as a
key, as imho that would be a bit weird. Needless to say there's a bunch of
stuff to figure out, so feel free to submit an issue or PR.

------
mewwts
Hey guys, author of addict here. Would appreciate immensely if you would file
issues of problems you find. It's a very new project, that I cooked up after
work hours this week, and any input is good input! Thanks.

~~~
thruflo
I like .attribute syntax as much as the next developer but the mutate-on-get
behaviour strikes me as... undesirable.

~~~
ekimekim
He is explicitly copying the behaviour of the standard library defaultdict
here. One clear motivation is thus:

Suppose I have an empty defaultdict(list). I do the following:

    
    
        d = defaultdict(list)
        l = d['foo']
    

According to no-mutate-on-get semantics, we now have an empty list, and d is
still empty. So what happens if I do:

    
    
        l.append('bar')
    

You might expect the dict to now be populated with {'foo': ['bar']}. However,
the dict has no way of knowing (without a large amount of additional tricks)
whether the new list that it previously returned had been later modified. So
what would actually happen would be nothing, d would remain empty. Worse, now
your behaviour is different depending on whether or not the 'foo' key was
already present (if it was, you would've gotten a reference to the actual
value and mutated it, and d would have changed as a result).

As I see it, there are only two sane solutions to this problem: Either
immediately store every generated value so any subsequent mutation is saved
(the solution used here)...or require that your default values be immutable.

------
lifeisadance
Aaron Swartz did something like this years ago, except it's ultra simple and
doesn't include any of the weird recursive stuff, in his web.py: the Storage
class.

[https://github.com/webpy/webpy/blob/master/web/utils.py#L52](https://github.com/webpy/webpy/blob/master/web/utils.py#L52)

~~~
mewwts
Cool! I need the recursive stuff, though!

------
mewwts
So I've made some changes over the day. Some issues still stand, but as some
people pointed out it was unnecessary to store both attributes and items. Now
the get and set attribute simply calls get and set items. If you find the
time, feel free to add an issue along with you HN comment :-)

------
Argorak
A pleasure to see the Python community adopting Rubys drug-related naming
schemes.

~~~
rmc
Yeah, something professional, maybe?

~~~
rankam
Can I ask why you would want it to be "professional" and who deemed this not
"professional"? I mean, the language's name is derived from Monty Python - I
don't think anyone would consider that "professional". Hackers are notoriously
clever and comical - shouldn't our work reflect our culture?

"Professional", to me, is boring - it's what the pointy haired boss wants you
to be because anything remotely outside of the box doesn't conform to their
risk-averse norms.

~~~
rmc
Harmless jokes are one thing. Hard drugs are another. What if someone was
recovering from their life being almost destroyed by heroin?

~~~
rankam
Then they have bigger and tougher problems than worrying about a hacker who
created a python library thats adds to dicts - ADDICT. Seriously, are we
supposed to censor ourselves on the off chance that someone is a recovering
addict? Should we ask the flask developers to rename their web framework to
ensure that it doesn't trigger an alcoholic to relapse?

You can be empathetic towards those who struggle with addiction and use the
word in a witty or comical fashion. The two are not mutually exclusive.

~~~
rmc
But what if it's used in a company? It's not nice for the company to force an
employee to use this.

We're talking one line in a description (and possibly the name, though that's
less impactful). Changing it doesn't change the programme. Why do some people
get _so_ sensitive about something so trivial and not want to change it?

~~~
rankam
It's not nice for a company to force an employee to use a library that may or
may not offend them but it is nice to ask the person who created that library
to change their work to conform to the company for which they likely do not
work? That sounds a bit self-centered.

"Why do some people get so sensitive about something so trivial and not want
to change it?"

You're joking, right? You're sensitive about the name of something you didn't
create and want it changed but it would be wrong for the author to not want to
change it because you're sensitive about it?

~~~
mewwts
Hey guys. Relax. I made up the "better than heroin"-part in a flash. Was not
trying to be offensive, I just liked it since it worked well with 'addict'!

------
phazelift
That's funny, I'm working on something similar for Javascript:
[https://www.npmjs.com/package/xs.js](https://www.npmjs.com/package/xs.js)

~~~
sysk
What if a key has a space?

------
PythonicAlpha
I use some similar classes in my projects.

It always has frustrated me to see that attribute- and dictionary access is so
similar but still different. Of course in most cases, there are some good
reasons to have it different -- but sometimes there are also good reasons to
generalize access to some important values.

So, we have duck typing and car typing. And both together we have a duck-car,
because it quacks like a duck and runs like a car.

------
Demiurge
This looks like something I really wish was just baked into the language. I
think there is a reason it isn't, like in JavaScript, and that is because
Python isn't as weakly typed, and encourages explicitness. So, I wouldn't use
it and would discourage everyone working with me from using this.

------
pjkundert
[https://github.com/pjkundert/cpppo/blob/master/dotdict.py](https://github.com/pjkundert/cpppo/blob/master/dotdict.py)

Quite complete attribute accessible dict, with iteration by full-depth keys.
Very useful for human readable output.

------
RayVR
this is an idea that I've seen used many times in the past. One of the first
refs I saw to this mashup of the keys of a dict and the values in __dict__ was
on Activestate in 2006 or so. There are also multiple versions on
Stackoverflow.

I'm reminded of the web designer example here
([https://gist.github.com/fmeyer/289467](https://gist.github.com/fmeyer/289467))
line 89.

I know that people use github for many things, including tracking their config
files, etc. However, this seems to be a post by the owner of the repo. Would
you ever, I mean _ever_ be happy if someone required this sort of 'library' as
a dependency in a project? I know I would not.

~~~
RayVR
An example that you should try: d = addict() d['__dict__'] = None

------
rcarmo
I've been using a similar thing I call Struct for ages, as part of my utils
package:

[https://github.com/rcarmo/python-
utils/blob/master/core.py](https://github.com/rcarmo/python-
utils/blob/master/core.py)

Can't really see this as newsworthy, sorry.

Edited to add: didn't mean to sound mean (if you'll pardon the pun), just
factual. There are ActiveState recipes out there for this sort of thing since
the beginning of time... Also, check my other comments for thoughts on syntax
and immutability.

~~~
lmartel
Does that last sentence contribute anything?

"Neat, I did something similar here: [link]" communicates the same
information. If it's not newsworthy it'll fall off the front page and that'll
be that.

I'm sure you didn't mean to be hurtful, but `Show HN` posts almost always end
up with a broad, critical top comment. Constructive criticism is great but
"this is trivial" just seems discouraging.

~~~
rcarmo
Didn't mean to come across as mean, merely factual.

There are plenty of similar implementations around, in fact, and this is the
kind of thing I've also seen as part of ActiveState recipes.

------
cjbprime
Hey, this looks super useful. Thanks for posting!

~~~
mewwts
Thanks man, glad you like it.

------
personjerry
I thought AdDict would be a dictionary whose values automap to Google Adsense
;)

------
Cieplak
Maybe consider creating a pull request against the standard python library.
This is really nice. This feels like it belongs in the collections module
alongside defaultdict.

~~~
the_mitsuhiko
No it really shouldn't. The usage is highly problematic because of naming
conflicts and the implicit default behavior. This causes more problems than
it's worth.

~~~
Cieplak
I'm not suggesting that it override the default dictionary behavior, but that
it would be nice to have the option of using javascript-like object syntax by
importing some special dict class. This library is really young, but I've
always thought this sort of feature would be nice syntactic sugar,
particularly for configuration objects. I find myself using namedtuples for
this sort of thing, but it would be nice to have the option of using an ad-hoc
data structure like this that's inherently trivial to serialize.

~~~
the_mitsuhiko
> but that it would be nice to have the option of using javascript-like object
> syntax by importing some special dict class

It's dangerous to use because it needs recursive collapsing to a regular dict
otherwise it's not safe to pass to many APIs. So yeah, it should definitely
not exist in the standard library.

~~~
Cieplak
If you are Armin, I want to take a moment to say I love your work. This ruby-
like dict class probably doesnt belong in the std python lib, but it is nice
to have in pypi because of the syntactic sugar. No denying that it is kind of
a leaky abstraction, one might even say a hack, but it has the potential to be
useful for concisely expressing nested key-value objects, as long as the
object instantiation is explicit so a reader would immediately know what
they're working with. It's really easy to implement something like this by
inheriting from dict and setting some magic methods, but I feel like I find
myself implementing Struct classes like this for myself because it can be more
elegant sometimes than nested dicts. If the python community agrees on the
correct semantics for a defaultdict-like object with __getattr__ and
__settattr__ implementations, it might be more elegant than the status-quo for
ad-hoc objects: [http://stackoverflow.com/questions/2827623/python-create-
obj...](http://stackoverflow.com/questions/2827623/python-create-object-and-
add-attributes-to-it)

------
anentropic
why do people keep insisting on inventing this ugly and pointless 'solution'?
too much Javascript?

just use `mydict.setdefault()` and `defaultdict` and give up on the whole attr
access thing. it's a dict

~~~
mewwts
I think the example on the README should make it pretty clear.

------
michaelmior
*whose values

~~~
mewwts
thanks :-)

