Live data from Hacker News

Don't let dicts spoil your code

roman.pt

11–20 of 121 posts

Re: Don't let dicts spoil your code

#11
post #7

Seems like the issue is less using dicts than not treating external APIs as input that needs to be sanitized.

Agreed. If you sanitize/allowlist API data you should not have issues with dicts.

You'll have issues if you ever rename things in the dict.

Linting tools will pick up on every instance where you forgot to rename the fields of a class, but won't do the same for dicts.

Re: Don't let dicts spoil your code

#12
post #8

Lists and sets suffer the same drawbacks. If the advice is to not use any of the batteries included if the language, why are we using Python? If you want an immutable mapping, why not use an enum?

This isn't arguing against them in general, but against the unfortunate Javascript-esque abandonment of specified semantics.

In particular, whenever anyone thinks that "deep clone vs shallow clone" is a meaningful distinction, that means their types are utterly void of meaning.

Re: Don't let dicts spoil your code

#13
post #11
post #7

Earlier quoted context omitted.

Agreed. If you sanitize/allowlist API data you should not have issues with dicts.

You'll have issues if you ever rename things in the dict. Linting tools will pick up on every instance where you forgot to rename the fields of a class, but won't do the same for dicts.

TypedDicts solve the linting problem, but refactoring tools haven't caught up (unlike e.g. ForwardRef type annotations, which are strings but can be transformed alongside type literals).

Re: Don't let dicts spoil your code

#14
Here’s an out-there take, but one I’ve held loosely for a long time and haven’t shed yet: dicts are not appropriate for what people mostly use them for, which is named access to member attributes.

dict is an implementation of a hash table. Hash table are designed for o(1) lookup of items. As such, they are arrays which are much bigger than the number of items they store, to allow hashing items into integers and sidestep collisions. They’re meant to act like an index that contains many records, not a single record.

A single record is more like a tuple, except you want named access instead of, title = movie[0], release_year = movie[1], etc. And Python had that, in NamedTuple, but it was kinda magical and no one used it (shoutout Raymond Hettinger).

Granted, this rant is pretty much the meme with the guy explaining something to a brick wall, in that dicts are so firmly entrenched as the "record" type of choice in Python (but not so in other languages: struct, case class, etc. and JSON doesn’t just deserialize to a weak type but I digress).

Re: Don't let dicts spoil your code

#15

For better or for worse, Python doesn't do typing well. I don't disagree that I prefer well defined types, but if that is your desire then I think Python is perhaps not the correct choice of language.

Python does typing pretty darn well now for data like API requests and responses.

"Typed Python" does poorly (compared to e.g. Typescript) on things like overloading functions, generics, structural subtyping, et al.

Re: Don't let dicts spoil your code

#16

Here’s an out-there take, but one I’ve held loosely for a long time and haven’t shed yet: dicts are not appropriate for what people mostly use them for, which is named access to member attributes. dict is an implementation of a hash table. Hash table are designed for o(1) lookup of items. As such, they are arrays which are much bigger than the number of items they store, to allow hashing items into integers and sides…

I think most modern Python codebases are using dataclasses/ something like Pydantic. I think dicts are mostly seen, like the author suggests, because something which you hacked up to work quickly ends up turning into actual software and it's too much work refactor the types

Re: Don't let dicts spoil your code

#17
This is absolute key advice.

Another way to look at it is the functional core, imperative shell pattern.

Wrapping up your dict in a value object (dataclass or whatever that is in you language) early on means you handle the ugly stuff first. Parse don't validate. Resist the temptation of optional fields. Is there really anything you can do if the field is null? No, then don't make it optional. Let it crash early on. Clearly define you data.

If you have put your data in a neat value objects you know what is in it. You know the types. You know all required fields are there. You will be so much happier. No checking for null throughout the code, no checking for empty strings. You can just focus on the business logic.

Seriously so much suffering can be avoided by just following this pattern.

Re: Don't let dicts spoil your code

#18

I think one really nice thing about Python is duck typing. Your interfaces are rarely asking for a dict as much as they’re asking for a dict-like. It’s pretty great how often you can worry about this kind of problem at the appropriate time (now, later, never) without much pain. There’s useful ideas in this post but I’d be careful not to throw the baby out with the bath water. Dicts are right there. There’s dict liter…

Duck typing is so fragile… Once you have implementations that are depending on your naming or property structure, you can’t update the model without breaking them all.

If you use a real type, you never have to worry about this.

Re: Don't let dicts spoil your code

#19
post #11

Earlier quoted context omitted.

You'll have issues if you ever rename things in the dict. Linting tools will pick up on every instance where you forgot to rename the fields of a class, but won't do the same for dicts.

TypedDicts solve the linting problem, but refactoring tools haven't caught up (unlike e.g. ForwardRef type annotations, which are strings but can be transformed alongside type literals).

Is there any advantage to using a TypedDict for a record over a dataclass?

Re: Don't let dicts spoil your code

#20
It's a bit of an odd article because the second part kind of shows why dicts aren't a problem. You basically just need to apply the most old school of OO doctrines: "recipients of messages are responsible for how they interpret them", and that's exactly what the author advocates when he talks about treating dict data akin to data over the wire, which is correct.

If you're programming correctly and take encapsulation seriously, then whatever shape incoming data in a dict has isn't something you should take an issue with, you just need to make sure if what you care about is in it (or not) and handle that within your own context appropriately.

Rich Hickey once gave a talk about something like this talking about maps in Clojure and I think he made the analogy of the DHL truck stopping at your door. You don't care what every package in the truck is, you just care if your package is in there. If some other data changes, which data always does, that's not your concern, you should be decoupled from it. It's just equivalent to how we program networked applications. There are no global semantics or guarantees on the state of data, there can't be because the world isn't in sync or static, there is no global state. There's actually another Hickey-ism along the lines of "program on the inside the same way you program on the outside". Dicts are cool, just make sure that you're always responsible for what you do with one.

Post reply on HN