Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Show HN: Addict – a Python dict whos values can be get and set using attributes (github.com/mewwts)
80 points by mewwts on Dec 13, 2014 | hide | past | favorite | 72 comments


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__


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'


> 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)
  {}


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()).


Oh thanks!


But if you do it like that, it's almost not worth packaging into a library and posting to hackernews. :)


This doesn't trigger autocompletion of attributes in dreampie / bpython / ipython - the original implementation does.


This is true!


This is neat! Super close to what I want in much less code :-)


You think right.


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.


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.


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).


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


Implementing an entire 3rd party python library with behavior almost the same as a defaultdict is unpythonic because a reader of the code base now has to learn an entirely new library for functionality that could be represented in a 10 line extension to defaultdict.


The whole "it must be pythonic" thing bothers me so much sometimes.

Its a shortcut to dismiss someone's views. Having an opinion rejected with a "import this, line N" is... such an annoyance. Some people treat the damn thing like a strict bible


I disagree. "Pythonic" and "Unpythonic" are inherently subjective and are always used as an expression of opinion regarding the consistency of some code, routine or library in relation to the larger body of Python.

Unless it is written PEP8 or by Guido, I would never take a "pythonicism" to be nothing more than short-hand for "I think this is (in)consistent with the larger patterns and traditions of Python as I understand them."


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.


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?


It will if you recursively define it:

    from collections import defaultdict
    def dd(): return defaultdict(dd)
    d = dd()
    d['a']['b']['c']  # => defaultdict(dd, {})
    d['a']['b']['c'] = 'test'
    d['a']['b']['c']  # => 'test'


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.


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.


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.


I haven't tried it but here's some issues from a superficial code review:

This uses twice as much memory as it needs to (and opens the door to potential inconsistency) by storing everything redundantly in both the attributes dictionary and the underlying dict.

Setting an item with a non-string key (e.g. An int or even unicode) is silently ignored.

    myDict[u'foo'] = 'bar'  # <- this ought to do something
Setting or deleting an item with the same name as an existing attribute (e.g. the name of a method) will replace or delete that attribute.

    myDict['get'] = 'foo'  # <- should not replace the method myDict.get
This can't be initialised with kwargs or an iterator like an ordinary dict can.


Thanks so much for your input. All of this is now fixed!


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


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.


Yes, .attribute syntax is much nicer than brackets, especially on German keyboard layouts (needs "Alt Graph"). Why can't dicts just support both? Because of the Zen? Also this is an old issue, see Alex Martelli's bunch class recipe on ActiveState http://code.activestate.com/recipes/52308-the-simple-but-han...

The mutate-on-get makes it very similar to how Matlab does its "structs", I'm mostly undecided on that but at times it can be nice.


How does this compare to AttrDict and treedict?


AttrDict only lets you get items using attributes. addict lets you set them as well!


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


Cool! I need the recursive stuff, though!


Simpler:

  class Storage:
    pass


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 :-)


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


I’m afraid I find it thoroughly unpleasant :(


Yeah, something professional, maybe?


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.


Addiction is a serious and life-threatening issue for some, why do you make light of such a seriously dangerous thing?

Pythons, on the other hand...


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


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.


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?


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?


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'!


Description is "The Python Dict that's better than heroin.".

Never utter that in front of someone who has serious experience with heroin or the repercussions.

Nevertheless, I was more playing with the fact that the number of drug-named libraries in Ruby was sometimes used for cheap shots at the community.


Haha :-)


That's funny, I'm working on something similar for Javascript: https://www.npmjs.com/package/xs.js


What if a key has a space?


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.


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.


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.


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) 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.


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


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

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.


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.


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.


Hey, this looks super useful. Thanks for posting!


Thanks man, glad you like it.


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


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.


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.


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.


What I'd really like to see is new syntax for item access. Transform a::b into a['b'].

Items and attributes are different, and failing to distinguish can make your code fragile - for example, it looks like Dict().get will return the attribute, unlike Dict()['get']. And if a new python version adds a new method to dict, some of your uses of Dict() will now break.

But item access is ugly when dealing with nested JSON or any number of other things, and that leads to people like me using terrible hacks that ambiguate between the two.


> 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.


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...


I agree with this. Even though I've used my Struct thing for a long while now _and_ subclassing dict directly in my case, I've been bitten by similar issues.

The syntactic sugar is very nice and saves up on oodles of brackets and quotes for nested objects, but if you try to mutate this sort of object you'll eventually run into trouble.


That (use as a config object) is exactly why I did my Struct thing. But if I were to do it again, I'd certainly make it immutable (although that would complicate the implementation somewhat)


Hi, the naming conflicts should now be fixed :-)


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


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


*whose values


thanks :-)




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: