[HN Gopher] Dict Unpacking in Python
       ___________________________________________________________________
        
       Dict Unpacking in Python
        
       Author : _ZeD_
       Score  : 131 points
       Date   : 2025-07-08 16:05 UTC (4 days ago)
        
 (HTM) web link (github.com)
 (TXT) w3m dump (github.com)
        
       | zdimension wrote:
       | Did not know that such things could be accomplished by
       | registering a new file coding format. Reminds me of
       | https://pypi.org/project/goto-statement/
        
         | zahlman wrote:
         | This one is arguably even more of a hack; it's working at the
         | source code level rather than the AST level.
         | 
         | The "coding" here is a bytes-to-text encoding. The Python lexer
         | expects to see character data; you get to insert arbitrary code
         | to convert the bytes to characters (or just use existing
         | schemes the implement standards like UTF-8).
        
           | almostgotcaught wrote:
           | > it's working at the source code level rather than the AST
           | level.
           | 
           | this (lexing) is the _only_ use of the codec hack - if you
           | want to manipulate the AST you do not need this and can just
           | to `ast.parse` and then recompile the function.
        
             | zahlman wrote:
             | Indeed; the goto hack works that way (and uses a decorator
             | to make it easier to invoke the AST-manipulation logic).
        
         | crabbone wrote:
         | I think there's a package to treat Jupyter notebooks as source
         | code (so you can import them as modules).
         | 
         | While the OP package is obviously a joke, the one with
         | notebooks is kind of useful. And, of course, obligatory quote
         | about how languages that don't have meta-programming at the
         | design level will reinvent it, but poorly.
        
           | Y_Y wrote:
           | You talking about this?
           | 
           | https://jupyter-
           | notebook.readthedocs.io/en/stable/examples/N...
        
           | xg15 wrote:
           | I'd argue "import from notebooks" is still only helpful in
           | the "space bar heating" sense.
           | 
           | I think Notebooks are great for quick, "explorative" sketches
           | of code. They are absolutely terrible for organizing
           | "production" code.
           | 
           | I know it often happens that something starts in a notebook
           | and then sort of morphs into a generic script or full-on
           | application. But I think, this is usually the signal you
           | should refactor, pull out the "grown" parts from the
           | notebooks and organize them into proper Python modules.
           | 
           | If you have parts that are still experimental or explorative,
           | consider importing your new modules _into the notebook_
           | instead of the other way around.
           | 
           | Source: personal experience
        
       | nine_k wrote:
       | In short, it runs a text preprocessor as the source text decoder
       | (like you would decode from Latin-1 or Shift-JIS to Unicode).
        
         | agumonkey wrote:
         | yeah that's the funny part here, would never have thought of
         | this
        
       | zelphirkalt wrote:
       | I found dictionary unpacking to be quite useful, when you don't
       | want to mutate things. Code like:                   new_dict =
       | {**old_dict, **update_keys_and_values_dict}
       | 
       | Or even complexer:                   new_dict = {
       | **old_dict,             **{                 key: val
       | for key, val in update_keys_and_values_dict                 if
       | key not in some_other_dict             }         }
       | 
       | It is quite flexible.
        
         | peter422 wrote:
         | I love the union syntax in 3.9+:                 new_dict =
         | old_dict | update_keys_and_values_dict
        
           | parpfish wrote:
           | Don't forget the in place variant!                 the_dict
           | |= update_keys_and_values_dict
        
             | masklinn wrote:
             | No no, do forget about it: like += for lists, |= mutates
             | "the dict", which often makes for awkward bugs.
             | 
             | And like += over list.extend, |= over dict.update is very
             | little gain, and restricts legal locations (augmented
             | assignments are statements, method calls are expressions
             | even if they return "nothing")
        
               | IgorPartola wrote:
               | The |= does exactly what it says on the tin. How could it
               | not mutate the left side of the assignment?
        
               | parpfish wrote:
               | In typed languages, I'm all about using nice safe
               | immutable variables/values.
               | 
               | But in python, everything is mutable so there's only so
               | much safety you can wring out of adhering the an
               | immutable style. Any other function can hop in and start
               | mutating things (even your "private" attributes). Plan
               | for mutations occurring everywhere.
        
               | graemep wrote:
               | I find the fewer mutations the easier code is to
               | understand, at the level of an individual function.
               | 
               | Of course you do not have the safety you would have in a
               | language that enforces immutability, but there is still a
               | cost (in terms of maintenance and the likelihood of bugs)
               | to mutation.
        
               | masklinn wrote:
               | > The |= does exactly what it says on the tin. How could
               | it not mutate the left side of the assignment?
               | 
               | The normal way? If the LHS is an integer. |= updates the
               | binding but does not mutate the object.
               | 
               | Nothing requires that |= mutate the LHS let alone do so
               | unconditionally (e.g. it could update the LHS in place as
               | an optimisation iff the refcount indicated that was the
               | only reference, which would optimise the case where you
               | create a local then update it in multiple steps, but
               | would avoid unwittingly updating a parameter in-place).
               | 
               | edit: you might not be understanding what dict.__ior__ is
               | doing:                 >>> a = b = {}       >>> c = {1:
               | 2}       >>> b |= c       >>> a       {1: 2}
               | 
               | That is, `a |= b` does not merely desugar to `a = a | b`,
               | dict.__ior__ does a `self.update(other)` internally
               | before updating the binding to its existing value. Which
               | also leads to this fun bit of trivial (most known from
               | list.__iadd__ but "working" just as well here):
               | >>> t = ({},)       >>> t[0] |= {1: 2}       Traceback
               | (most recent call last):         File "<stdin>", line 1,
               | in <module>       TypeError: 'tuple' object does not
               | support item assignment       >>> t       ({1: 2},)
        
       | sco1 wrote:
       | The author also has an accompanying video:
       | https://youtu.be/eqiM0xRmFJg
        
       | andy99 wrote:
       | def u(**kwargs):         return tuple(kwargs.values())
       | 
       | Am I missing something, is this effectively the same?
       | 
       | *I realize the tuple can be omitted here
        
         | Grikbdl wrote:
         | Yours relies on ordering, OP's presumably does not.
        
         | Izkata wrote:
         | You have to pull them out by key name, and not just get
         | everything. Here's a working version, though with a totally
         | different syntax (to avoid having to list the keys twice, once
         | as keys and once as resulting variable names):
         | >>> def u(locals, dct, keys):       ...     for k in keys:
         | ...         locals[k] = dct[k]       ...        >>> dct =
         | {'greeting': 'hello', 'thing': 'world', 'farewell': 'bye'}
         | >>> u(locals(), dct, ['greeting', 'thing'])       >>> greeting
         | 'hello'       >>> thing       'world'       >>> farewell
         | Traceback (most recent call last):         File "<stdin>", line
         | 1, in <module>       NameError: name 'farewell' is not defined
         | 
         | Modifying locals() is generally frowned upon, as there's no
         | guarantee it'll work. But it does for this example.
        
         | sischoel wrote:
         | Or use itemgetter:                 >>> from operator import
         | itemgetter       >>> dct = {'greeting': 'hello', 'thing':
         | 'world', 'farewell': 'bye'}       >>> thing, greeting =
         | itemgetter("thing", "greeting")(dct)       >>> thing
         | 'world'       >>> greeting       'hello'
        
           | giingyui wrote:
           | There are so many things like this one in the standard
           | library that it kinda pisses me off when I discover a new
           | one.
        
           | notpushkin wrote:
           | Ohhh, nice. Also, attrgetter (which also supports dotted
           | notation to get nested attrs! Sadly, no dotted notation for
           | itemgetter.)
           | 
           | https://docs.python.org/3/library/operator.html#operator.att.
           | ..
        
         | masklinn wrote:
         | TFA looks things up by key, and allows pulling a subset of the
         | dict.
        
       | kristjansson wrote:
       | While not nearly as fun as the OP, I'd note that this sort of
       | unpacking is very pleasant in the newish PEP636 match case
       | statements:
       | 
       | https://peps.python.org/pep-0636/#matching-builtin-classes
        
         | xg15 wrote:
         | Looks really cool!
         | 
         | Will this allow combinations of bound and unbound variables?
         | 
         | E.g.:                 def is_on_horizontal_line(point, line_y):
         | match point:           case (x, line_y):             return
         | f"Yes, with x={x}"           case _:             return "No"
         | 
         | Seems both useful and potentially confusing.
        
           | thayne wrote:
           | It allows you to use bound variables/constants as long as the
           | expression includes a dot so you can distinguish it from a
           | capture variable.
           | 
           | Scala allows matching against bound variables but requires it
           | either start with an uppercase letter or be surrounded in
           | backtics in the pattern. I don't know that that would make
           | sense for python, but there could potentially be some special
           | syntax to spicify you want to compare against an existing
           | variable instead of capturing a new variable.
        
             | xg15 wrote:
             | Ah, that makes sense. Maybe the "exceptions" (dots,
             | uppercase letters, etc) are needed to permit bound
             | variables that we usually don't think of as variables at
             | all, like class or package identifiers?
        
       | nikisweeting wrote:
       | I would donate $500 to the PSF tomorrow if they added this, the
       | lack of it is daily pain
        
         | almostgotcaught wrote:
         | you can't do this consistently across all cases without
         | compiler assistance (see https://doc.rust-
         | lang.org/book/ch19-03-pattern-syntax.html or
         | https://peps.python.org/pep-0636/#matching-builtin-classes
         | linked below).
        
           | nikisweeting wrote:
           | perfect is enemy of good imo, dict destructuring is so
           | valuable that I'm willing to bend some rules / add some rules
           | to make it possible. can't we just copy whatever JS does?
        
             | skeledrew wrote:
             | If it's that valuable to you personally you can use that
             | project to remove your "daily pain". No need to inflict the
             | pain caused by such a thing being present in official
             | Python. Some of us like for the language to remain highly
             | readable.
        
               | notpushkin wrote:
               | > you can use that project
               | 
               | It's not meant for production use. Quite clearly so:
               | https://github.com/asottile/dict-unpacking-at-
               | home#please-do...
        
             | almostgotcaught wrote:
             | > perfect is enemy of good imo
             | 
             | You can't land a language feature that only sometimes works
             | - that's absolutely horrid UX.
             | 
             | > can't we just copy whatever JS does?
             | 
             | I wasn't aware that js does this and I don't know it's
             | implemented. So maybe I should retract my claim about
             | compiler assistance.
        
               | nikisweeting wrote:
               | It's been one of the headline features keeping me happy
               | in JS for 9+ years.
        
         | IshKebab wrote:
         | You shouldn't be using dicts for data that you know the name of
         | anyway - use dataclasses or named tuples. Dicts are best for
         | things with keys that are not known at compile time.
        
           | IgorPartola wrote:
           | Since when can you use data classes for kwargs? There are
           | plenty of times when you should use a dict even if you know
           | the keys.
        
             | IshKebab wrote:
             | You shouldn't be using kwargs! That is also well known to
             | be bad practice (or it should be anyway).
             | 
             | https://medium.com/codex/stop-using-kwargs-as-method-
             | argumen...
             | 
             | http://ivory.idyll.org/blog/on-kwargs.html
             | 
             | Give me another one.
        
               | almostgotcaught wrote:
               | lol i think you didn't read/understand this - the article
               | is about **kwargs (which is sometimes sloppy) while the
               | person you're responding to is talking about "exploding"
               | a dict when calling a function (this does not require
               | **kwargs at all).
        
               | ahupp wrote:
               | I'd agree with this, unless the kwargs is typed with the
               | new-ish PEP-692: https://peps.python.org/pep-0692/
        
         | crabbone wrote:
         | Now come on... for code golf? Why on Earth would anyone want
         | _extra_ syntax in a language with already tons of bloat in the
         | syntax that contribute nothing to language 's capabilities?
         | It's, in Bill Gates words, like paying to make airplanes
         | heavier...
         | 
         | This package is a funny gimmick, to illustrate, probably,
         | unintended consequences of some of the aspects of Python's
         | parser. Using this for anything other than another joke is
         | harmful...
        
       | agumonkey wrote:
       | Coming from lisp/haskell I always wanted destructuring but after
       | using it quite a lot in ES6/Typescript, I found it's not always
       | as ergonomic and readable as I thought.
        
       | qwertox wrote:
       | This confuses me a bit                 dct = {'a': [1, 2, 3]}
       | {'a': [1, *rest]} = dct       print(rest)  # [2, 3]
       | 
       | Does this mean that i can use?                 dct = {'a': [1, 2,
       | 3]}       {'b': [4, *rest]} = dct       print(rest)  # [2, 3]
       | 
       | and more explicit                 dct = {'a': [1, 2, 3]}
       | {'_': [_, *rest]} = dct       print(rest)  # [2, 3]
        
         | qexat wrote:
         | None of the last two LHSes will match `dct`, so you'll get a
         | runtime error.
        
         | masklinn wrote:
         | > Does this mean that i can use?
         | 
         | They'll both trigger a runtime error, since the key you're
         | using in the pattern (LHS) does not match any key in the dict.
         | 
         | Note that `'_'` is an actual string, and thus key, it's not any
         | sort of wildcard. Using a bare `_` as key yields a syntax
         | error, I assume because it's too ambiguous for the author to
         | want to support it.
        
       | odyssey7 wrote:
       | Python needs a better dictionary. Also, Python needs better names
       | for things than _dict_.
        
       | yde_java wrote:
       | I use the Python package 'sorcery' [0] in all my production
       | services.
       | 
       | It gives dict unpacking but also a shorthand dict creation like
       | this:                   from sorcery import dict_of, unpack_keys
       | a, b = unpack_keys({'a': 1, 'b': 42})         assert a == 1
       | assert b == 42         assert dict_of(a, b) == {'a': 1, 'b': 42}
       | 
       | [0] https://github.com/alexmojaki/sorcery
        
         | john-radio wrote:
         | That seems a bit crazy and like it would lead to unpredictable
         | and hard-to-mantain code. (pardon my candor).
        
           | rrishi wrote:
           | im curios why you think so ?
        
       | notpushkin wrote:
       | So I see asottile has gone from backporting released features [1]
       | to backporting unreleased ones!
       | 
       | [1]: https://pypi.org/p/future-fstrings, mentioned in
       | https://github.com/asottile/dict-unpacking-at-home#please-do...
        
       | xg15 wrote:
       | Lame: Submit a PEP, campaign for community support, write a
       | patch, go back and forth with the maintainers, endure weeks and
       | months of bikeshedding, then maybe, eventually have your feature
       | included in the next Python release.
       | 
       | Game: Use the codec hack, immediately publish your feature for
       | all Python versions, then write "please do not use" to be safe.
        
       | sametmax wrote:
       | Anthony is also the maintainer of the deadsnake ppa, if you were
       | searching for reasons to love him more.
        
         | mixmastamyk wrote:
         | Believe he's the same person who won't allow pyflakes to
         | support # noqa, because it's "opinionated."
         | 
         | As if dropping that word is some sort of justification. I don't
         | know what the opinion is! Worse is better?
        
       | ziofill wrote:
       | The confusing bit to me is that the LHS of this
       | 
       | {greeting, thing} = dct
       | 
       | is a set, which is not ordered, so why would greeting and thing
       | be assigned in the order in which they appear?
        
         | xg15 wrote:
         | I don't think they are. They are matched by variable names, so
         | this:                 {thing, greeting} = dct
         | 
         | Should have the exact same result.
        
       | frollogaston wrote:
       | After using JS, Python dicts and objects feel so cumbersome. I
       | don't see why they need to be separate things, and why you can't
       | access a dict like `dict.key`. Destructuring is the icing on the
       | cake. In JS, it even handles the named args use case like
       | const foo = ({name, age, email}) => { }
       | 
       | I'm guessing all of this has been proposed in Python before, and
       | rejected in part because at this point it'd create way more
       | confusion than it's worth.
        
       ___________________________________________________________________
       (page generated 2025-07-12 23:01 UTC)