Hard Python Dictionary Exercise

23 days ago by bterwijn to c/python

A hard exercise to help build the right mental model for Python data.

The โ€œSolutionโ€ link visualizes execution and reveals whatโ€™s actually happening using ๐—บ๐—ฒ๐—บ๐—ผ๐—ฟ๐˜†_๐—ด๐—ฟ๐—ฎ๐—ฝ๐—ต: https://github.com/bterwijn/memory_graph

eleijeep 20 points 22 days ago

The | and |= operators for dict were added in 3.9 which was released in 2020, so it's a relatively recent feature.

I like having an operator for set union but I don't like that a |= b has different semantics to a = a|b. This violates the principle of least surprise, since the former is supposed to be a shorthand for the latter.

path: 0 26010747, hotness: undefined, score: 20, children: 13
bterwijn 6 points 22 days ago

The difference between a |= b and a = a | b is exactly the same as the difference between a += b and a = a + b. Nothing new about that, try:

a = [1]
b = a
b += [2]
b.append(3)
b = b + [4]
b.append(5)

print(a)

or see: https://fosstodon.org/@bterwijn/116328991093782469

For immutable types there is no difference between a += b and a = a + b, try with a tuple.

path: 0 26010747 26011278, hotness: undefined, score: 6, children: 12
sukhmel 7 points 22 days ago

I feel that things like this lead people to say something along the lines of โ€˜mutability was a mistakeโ€™

I'm only half serious, but this is really surprising without Python background

path: 0 26010747 26011278 26017662, hotness: undefined, score: 7, children: 11
bterwijn 3 points 22 days ago

If you don't want mutability you have to go to a pure functional language like Haskell, but then you have to copy a big list every time you make a change. There are ways to optimize copying by secretly sharing data behind the scene but you pay a performance price in some way. Then again, Python is slow and has mutability but popular for other reasons.

path: 0 26010747 26011278 26017662 26018427, hotness: undefined, score: 3, children: 10
a_non_monotonic_function 4 points 22 days ago

Like anything else, these sorts if issues are rather murky and are directly impacted by the user's competence in the tools.

Does python have obvious overhead issues? Yes.

Does Python have to be super inefficient? No.

Take basic set operations, for example. Do it manually in the language and it will be dog slow. Do it using the set class? You are leveraging the speed of the underlying C implementation.

E.g., In competitive programming eventually need C or Java, but a strong Python user can move the bar significantly in terms of how many problems are possible with Python.

If you don't want mutability you have to go to a pure functional language like Haskell, but then you have to copy a big list every time you make a change. There are ways to optimize copying by secretly sharing data behind the scene but you pay a performance price in some way.

I don't believe this to be the case. The immutability is precisely why efficient structural sharing is possible without screwing up other data structures. And for standard stuff, it is actually happening behind the scenes already.

You see similar claims about recursion in general, but those claims are often so broad that they don't hold water. I mean, yea, if your language sucks at optimizing recursion it isn't going to be a pleasant experience, but tail call elimination, lazy evaluation, etc. mean you can write some really awesome and efficient code.

I think the bigger problem is that it takes a lot of time to internalize what is going on under the hood when you make a call or initialize a data structure.

path: 0 26010747 26011278 26017662 26018427 26018751, hotness: undefined, score: 4, children: 3
sukhmel 4 points 22 days ago

I don't think mutability is wrong as a concept, albeit I enjoyed learning and toying with Haskell, but I really think these functions in Python should have been two sets of operations, one set to do what a += b does and one set to do what a = a + b does.

I've been doing C++ for quite some time and amount of implicit things that happen magically and not everyone get them right and this leads to bugs and confusion had really grown on me with time, and I feel like this is the same pattern here, where we get implicit magic instead of being clear with intentions and results

path: 0 26010747 26011278 26017662 26018427 26022792, hotness: undefined, score: 4, children: 5
MrWrinkles 11 points 22 days ago

As a C programmer, this reads as a shitpost lol. Best I can do is interpret this in Tcl, but I'm lost with |=

path: 0 26010135, hotness: undefined, score: 11, children: 4
bterwijn 3 points 22 days ago path: 0 26010135 26010522, hotness: undefined, score: 3, children: 2
MrWrinkles 3 points 22 days ago

That's really good. I love the play feature. This motivates me to learn more Python.

path: 0 26010135 26010522 26013283, hotness: undefined, score: 3, children: 1
bterwijn 2 points 22 days ago

Thanks, I'm pretty happy with it myself.

path: 0 26010135 26010522 26013283 26013453, hotness: undefined, score: 2, children: 0
a_non_monotonic_function 2 points 22 days ago

C already has that class of operators, but lacks a set in the base language.

You are probably interacting with analogous operators all day long.

I don't see why extending those notions would cause you issues as a C programmer.

path: 0 26010135 26018803, hotness: undefined, score: 2, children: 0
apparia 6 points 22 days ago

Nice puzzle and tool. I picked the right answer but I wasn't overly confident.

You could list {1: [100]} as another possible answer, if you assume: ::: spoiler spoiler b |= {...} is equivalent to b = b | {...}, but you also realise the lists are never deep-cloned. :::

path: 0 26017588, hotness: undefined, score: 6, children: 1
bterwijn 4 points 22 days ago

Thanks, maybe I should have added that possible answer instead of 'D', adding one more feels too many.

path: 0 26017588 26018461, hotness: undefined, score: 4, children: 0
farmgineer 6 points 22 days ago

My brain threw a compile error on |=. I have no idea what that does without looking it up (I've basically never touched python having mostly worked in perl/shell back in the day and I rarely need scripting languages today). I'm also not sure what the pipe operator on its own is doing there.

path: 0 26023432, hotness: undefined, score: 6, children: 5
syklemil 5 points 21 days ago

I didn't have previous exposure to that exact syntax either, but:

  • Python and plenty other languages have assignment variations with an operator like +
  • | in plenty of languages means logical OR
    • possibly bitwise, but that seems unlikely in Python
  • Dicts are sorta complex siblings of sets
  • In terms of sets, OR is equivalent to a union

Hence, a |=b should have the same meaning as a = a | b or a = a.union(b)

and then the quiz is about basically implementation details and what the actual semantics winds up being in a language like python, where stuff is mutable by default and references are implicit (even though the zen of python states a preference for being explicit)

path: 0 26023432 26026424, hotness: undefined, score: 5, children: 3
bterwijn 3 points 21 days ago

There is one important difference between a |= b and a = a | b, that is one part that makes this exercise difficult. See the dictionary docs (https://docs.python.org/...):

d |= other Update the dictionary d with keys and values from other, which may be either a mapping or an iterable of key/value pairs. The values of other take priority when d and other share keys. Added in version 3.9.

Pick your answer and check the Solution link.

The zen of Python lies unless you're Dutch. I'm Dutch and when I look out of my office I see the CWI where Guido wrote the original Python versions back in the day, see "Python the documentory" on YouTube.

path: 0 26023432 26026424 26031410, hotness: undefined, score: 3, children: 2
syklemil 2 points 21 days ago

I was trying not to spoil that a |= b and a = a | b have different semantics though, as that pretty much winds up giving away the answer IMO

path: 0 26023432 26026424 26031410 26037724, hotness: undefined, score: 2, children: 1
bterwijn 1 point 21 days ago

Thanks, but I also give a solution link, so people can already cheat if they really want too.

path: 0 26023432 26026424 26031410 26037724 26038990, hotness: undefined, score: 1, children: 0
bterwijn 5 points 21 days ago

If you are interested see the dictionary docs (https://docs.python.org/...):

d |= other Update the dictionary d with keys and values from other, which may be either a mapping or an iterable of key/value pairs. The values of other take priority when d and other share keys. Added in version 3.9.

Pick your answer and check the Solution link.

path: 0 26023432 26031481, hotness: undefined, score: 5, children: 0
SpaceNoodle 3 points 23 days ago

First link is broken, second doesn't lead to what's claimed.

path: 0 26008300, hotness: undefined, score: 3, children: 3
bterwijn 2 points 22 days ago

Can you tell me what the problem is for you when you click the first link? I tried to make it work for all browsers (even mobile, but the UI isn't very mobile friendly), so I would like to get it working on your system too. If you can give me a bug report (and maybe try from different system), that would be great.

The second link explains:

It's left to the reader to generalize this to x |= y vs x = x | y.

path: 0 26008300 26010404, hotness: undefined, score: 2, children: 2
SpaceNoodle 2 points 22 days ago

path: 0 26008300 26010404 26010473, hotness: undefined, score: 2, children: 1
bterwijn 2 points 22 days ago

Diagnostics came up with some "double percent-encoding" issue on IOS. I've updated the website with a workaround, hopefully it does work now. Thanks for bug reporting.

path: 0 26008300 26010404 26010473 26011096, hotness: undefined, score: 2, children: 0
b34k 3 points 22 days ago

Not too familiar with the union operator for dicts, but that result was not what I expected.

path: 0 26009257, hotness: undefined, score: 3, children: 1
bterwijn 1 point 22 days ago

The dict union operator is similar to that of set, but in addition the right operand overwrites values of keys of the left operand. It's a nice operator once you get used to it.

Easy to make a mistake with this exercise:

  • first realize b = b | {3: []} makes a shallow copy
  • then realize the shallow copy still references the value of key 2 in a so we can still append to that and change a
path: 0 26009257 26010217, hotness: undefined, score: 1, children: 0
Daedskin 1 point 22 days ago

Couldn't get the solution page working on mobile (none of the buttons seemed to do anything); but my guess is it would be {1: []} due to |= silently using __or__ and an assignment, meaning b now holds a new reference

path: 0 26010580, hotness: undefined, score: 1, children: 1
bterwijn 5 points 22 days ago

This is what the solution link is supposed to give you at the end of the execution animation:

It's hard to support all mobile browsers, on some it does work.

path: 0 26010580 26010673, hotness: undefined, score: 5, children: 0
python
python

@programming.dev

login for more options
8076
745
294

Welcome to the Python community on the programming.dev Lemmy instance!

๐Ÿ“… Events
Past

November 2023

October 2023

July 2023

August 2023

September 2023

๐Ÿ Python project:
๐Ÿ’“ Python Community:
โœจ Python Ecosystem:
๐ŸŒŒ Fediverse
Communities
Projects
  • Pythรถrhead: a Python library for interacting with Lemmy
  • Plemmy: a Python package for accessing the Lemmy API
  • pylemmy pylemmy enables simple access to Lemmy's API with Python
  • mastodon.py, a Python wrapper for the Mastodon API
Feeds

go to feed...