40

I'm looking for an elegant way to extract some values from a Python dict into local values.

Something equivalent to this, but cleaner for a longer list of values, and for longer key/variable names:

d = { 'foo': 1, 'bar': 2, 'extra': 3 }
foo, bar = d['foo'], d['bar']

I was originally hoping for something like the following:

foo, bar = d.get_tuple('foo', 'bar')

I can easily write a function which isn't bad:

def get_selected_values(d, *args):
    return [d[arg] for arg in args]

foo, bar = get_selected_values(d, 'foo', 'bar')

But I keep having the sneaking suspicion that there is some other builtin way.

Lev Levitsky
  • 63,701
  • 20
  • 147
  • 175
DonGar
  • 7,344
  • 8
  • 29
  • 32
  • 2
    I'm sorry to ask, but *why* would you want to do that? – Tim Pietzcker Jul 19 '13 at 20:47
  • It may be worthwhile to have a look at a question about scoping and contexts that I worked on before: < http://stackoverflow.com/questions/12485837/getting-the-block-of-commands-that-are-to-be-executed-in-the-with-statement >. This might be overkill for you, but it was a good solution for me to be able to work with data objects unpacked from certain data structures and greatly simplify the syntax of applying math operations to them. – ely Jul 19 '13 at 20:56
  • 2
    I'm handling JSON structures in which 3-4 values are important for fairly complicated routing logic, but in which the original structure needs to just be passed along to the final processing. – DonGar Jul 19 '13 at 22:02
  • 1
    One reason for wanting to do this might be for unpacking a `namedtuple` into multiple variables in a single statement. E.g. `foo, bar = get_selected_values(some_func_returns_named_tuple(), 'foo', 'bar')` rather than `my_named_tuple = some_func_returns_named_tuple(); foo = my_named_tuple.foo; bar = my_named_tuple.bar` – davidA Feb 27 '17 at 01:42
  • Does this answer your question? [convert dictionary entries into variables - python](https://stackoverflow.com/questions/18090672/convert-dictionary-entries-into-variables-python) – ggorlen Nov 28 '21 at 16:03

5 Answers5

54

You can do something like

foo, bar = map(d.get, ('foo', 'bar'))

or

foo, bar = itemgetter('foo', 'bar')(d)

This may save some typing, but essentially is the same as what you are doing (which is a good thing).

Marcin
  • 48,559
  • 18
  • 128
  • 201
Lev Levitsky
  • 63,701
  • 20
  • 147
  • 175
4

Somewhat horrible, but:

globals().update((k, v) for k, v in d.iteritems() if k in ['foo', 'bar'])

Note, that while this is possible - it's something you don't really want to be doing as you'll be polluting a namespace that should just be left inside the dict itself...

Jon Clements
  • 138,671
  • 33
  • 247
  • 280
  • `globals().update((k, d[k]) for k in ['foo', 'bar'])` is a bit shorter. Apparently `globals()` is OK to update but [`locals()` isn't](https://docs.python.org/3/library/functions.html#locals). Ultimately, I agree that this is somewhat horrible. – ggorlen Nov 29 '21 at 05:51
  • note that this may have undesired results if your dict contains keys that match other globals (be careful). – Tcll Dec 03 '21 at 12:28
3

Well, if you know the names ahead of time, you can just do as you suggest.

If you don't know them ahead of time, then stick with using the dict - that's what they're for.

If you insist, an alternative would be:

varobj = object()
for k,v in d.iteritems(): setattr(varobj,k,v)

After which, keys will be variables on varobj.

Brian Peterson
  • 2,800
  • 6
  • 29
  • 36
Marcin
  • 48,559
  • 18
  • 128
  • 201
  • 1
    Oh mystery downvoter, I bet you can't say what's wrong with this. – Marcin Jul 19 '13 at 20:51
  • 2
    The scope of the question does not request nor require comments like "if you insist." I think it's safe to assume that the OP is aware of the risks (or that a discussion of the risks can go in the comments area to the question, and not as an answer). Sticking them as attributes on an object doesn't achieve the stated goal of making them local variables with some particular names. If you're willing to modify the tone of the answer, so that it does not qualify anything with best practices styled recommendations, I'd be happy to change the vote once I can. – ely Jul 19 '13 at 20:53
  • 4
    @EMS I'm sorry, but there's no rule that I have to tell OP what he wants to do, without evaluating whether or not it's a good idea. Frankly, your attitude is so offensive that I'd rather not have your vote. – Marcin Jul 19 '13 at 21:02
  • 2
    I disagree very strongly. There is an obligation to try to answer the OP's question as stated, and not to impose opinions about whether it is a good idea. Or at least leave them for comments instead of using answer space for expressing opinions about what is a good idea. I don't see how this view is offensive. I find the tone of your answer a bit offensive (the "if you insist" part particularly, as if it's wrong to want to try something). There can be scores of reasons to want to do something in code that isn't best practice, including just for pure pedagogy and exploration. – ely Jul 19 '13 at 21:06
  • 4
    @EMS Well, why don't you go start your own Q&A site, instead of trying to create your own code of conduct. – Marcin Jul 19 '13 at 21:09
  • 2
    I believe my description is more in line with SO's stated goals. If I did not think it was in line with SO's stated goals, I would not use votes or comments here to advocate for this position. – ely Jul 19 '13 at 21:09
  • 1
    @EMS "Believe" what you like. If there were such a rule, you'd no doubt be able to cite it. – Marcin Jul 19 '13 at 21:13
  • 2
    It is my interpretation of the "Answer the Question" and "Always Be Polite and Have Fun" items in the guide for writing a good answer: ([link](http://stackoverflow.com/help/how-to-answer)). If the OP had expressed some other goal, for which the local variables was the hoped-for-but-not-best-practice solution, then perhaps the "it's ok to say don't do that" part would apply. But that doesn't seem to be the case here. The OP seems to be asking precisely how to make local variables from a `dict`. Whether this is good or bad, it is the explicit question. – ely Jul 19 '13 at 21:16
  • 3
    It's an interesting answer, partly because it creates a new namespace. – DonGar Sep 10 '13 at 07:32
  • 1
    a less hacky approach (by design) would be to use a `ns = namespace()` from `namespace = sys.implementation.__class__` where you can `ns.__dict__.update( d.fromkeys( ('foo', 'bar') ) )` and access `ns.foo`. ;) – Tcll Jun 23 '19 at 02:54
  • This doesn't quite answer the question, but if you are going to take the approach of creating a namespace, [this answer](https://stackoverflow.com/a/36059129/6243352) shows a clean approach using SimpleNamespace. – ggorlen Nov 28 '21 at 16:02
0

If you're looking for elegance with performance across python versions, sadly I don't think you're gonna get any better than:

unpack = lambda a,b,c,**kw: (a,b,c) # static

d = dict( a=1, b=2, c=3, d=4, e=5 )

a,b,c = unpack(**d)

working examples:

>>> unpack = lambda a,b,c,**kw: (a,b,c)
>>> d = dict( a=1, b=2, c=3, d=4, e=5, f=6, g=7 )
>>> a,b,c = unpack(**d)
>>> 
>>> unpack(**d)
(1, 2, 3)

you can also unpack the difference that would otherwise be discarded:

>>> unpackextra = lambda a,b,c,**kw: (a,b,c,kw)
>>> a,b,c,extra = unpackextra(**d)
>>> 
>>> unpackextra(**d)
(1, 2, 3, {'f': 6, 'd': 4, 'e': 5, 'g': 7})

arguments are static though, so the use case should be specific to whatever your intents are. ;)

>>> unpack2 = lambda a,b,c,d,e,f,**kw: (a,b,c,d,e,f) # static
>>> 
>>> unpack2(**d)
(1, 2, 3, 4, 5, 6)
>>> 
Tcll
  • 7,140
  • 1
  • 20
  • 23
  • So if you want to unpack 1, 2, 3, 4, 5... items you have to write new functions for each one? PEP-8 also discourages assigning lambdas to variables like this. Finally, this still relies on ordering of the dict which is unsafe even in 3.6+: `c,b,a = unpack(**d)` gives the wrong values. – ggorlen Nov 28 '21 at 15:50
  • @ggorlen yes, you need to write a new function, this method assumes you have a pre-planned data structure to follow. and no, the dict is unordered, the order is applied via the function, there is no global solution to this across python versions. – Tcll Nov 29 '21 at 16:16
  • @ggorlen also note they key word to this answer is "performance" as argument unpacking is native functionality to python, not some builtin function. would be nice if you could just do `>>> a,b,c,**extras = d` directly, but python doesn't support this. – Tcll Dec 03 '21 at 12:36
-1

The elegant solution:

d = { "foo": 123, "bar": 456, None: 789 }
foo, bar, baz = d.values()  # 123, 456, 789

Notice that keys are not used, so be sure to get the order of your variables right, i.e. they must match the order that the keys were inserted into the map (this ordering is guaranteed since Python 3.6). If in doubt about ordering, use the other methods.

Tronic
  • 1,248
  • 12
  • 16
  • In the original question, part of the goal was to only use a subset of the values. – DonGar Jun 25 '19 at 18:13
  • This approach is wrong. It might fail because iterating over a dictionary does not guarantee to follow insertion order. That's why Python developers invented 'OrderedDict'. – Carlos Pinzón Feb 23 '21 at 11:00
  • 2
    @CarlosPinzón OrderedDict was needed in old Python versions. Now `dict` insertion order is guaranteed (it became part of the language specification in 3.7, and CPython 3.6 already kept the insertion order). – Tronic Mar 30 '21 at 15:01
  • @L.Kärkkäinen, you are right. I just searched for 'insertion order' in the docs, and they say so: https://docs.python.org/3.7/library/stdtypes.html#typesmapping – Carlos Pinzón Mar 30 '21 at 20:02
  • Even with guaranteed order, this seems brittle. It's generally not safe to think of dicts as ordered -- you'd not expect unpacking logic to be tied to that, so reordering the dict would surprisingly break code like this, but not other solutions in the thread. This is syntactically appealing, though. – ggorlen Nov 28 '21 at 15:47