6 ms·
Show HN: Addict – a Python dict whos values can be get and set using attributes
- Cieplak 12y agoMaybe 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 12y agoNo 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 12y agoI'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 12y ago> 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.
- rcarmo 12y agoI 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.
- Cieplak 12y agoIf 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-object-and-add-attributes-to-it http://stackoverflow.com/questions/2827623/python-create-obj...
- rcarmo 12y agoThat (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)
- philh 12y agoWhat 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.
- mewwts 12y agoHi, the naming conflicts should now be fixed :-)
- rcarmo 12y agoI'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 12y agoDoes 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 12y agoDidn'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 12y agoHey, this looks super useful. Thanks for posting!
- mewwts 12y agoThanks man, glad you like it.
- Argorak 12y agoA pleasure to see the Python community adopting Rubys drug-related naming schemes.
- rmc 12y agoYeah, something professional, maybe?
- rankam 12y agoCan 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.
- dsjoerg 12y agoAddiction 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...
- rmc 12y agoHarmless jokes are one thing. Hard drugs are another. What if someone was recovering from their life being almost destroyed by heroin?
- rankam 12y agoThen 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.
- ecma 12y agoThis 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 12y agoValid input, but a nested defaultdict won't let me recursively define items: a.b.c.d.e = 2 as far as I can tell?
- adambard 12y agoIt 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'
- ecma 12y agoIt 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 12y agoI 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 12y agoHey 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 12y agoI like .attribute syntax as much as the next developer but the mutate-on-get behaviour strikes me as... undesirable.
- czam 12y agoYes, .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-handy-collector-of-a-bunch-of-named/ 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.
- ekimekim 12y agoHe 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.
- 12y ago
- personjerry 12y agoI thought AdDict would be a dictionary whose values automap to Google Adsense ;)
- michaelmior 12y ago*whose values
- mewwts 12y agothanks :-)
- tyrion 12y agoI 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__
- BerislavLopac 12y agoYou think right.
- zb 12y agoAlmost, 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'
- deleted 12y ago[deleted]
- tyrion 12y ago> 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 12y agoThe 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()).
- nvader 12y agoOne 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 12y agoThe 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 12y agoI 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 12y agoYes, I agree, some measure should be made to avoid overriding methods. Thanks!
- hueving 12y agoImplementing 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.
- phazelift 12y agoThat'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 12y agoWhat if a key has a space?
- PythonicAlpha 12y agoI 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 12y agoThis 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 12y agohttps://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.
- lifeisadance 12y agoAaron 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
- anentropic 12y agowhy 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 12y agoI think the example on the README should make it pretty clear.
- mewwts 12y agoSo 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 :-)
- RayVR 12y agothis 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 12y agoAn example that you should try: d = addict() d['__dict__'] = None