14

I'm trying to write a function that turns strings of the form 'A=5, b=7' into a dict {'A': 5, 'b': 7}. The following code snippets are what happen inside the main for loop - they turn a single part of the string into a single dict element.

This is fine:

s = 'A=5'
name, value = s.split('=')
d = {name: int(value)}

This is not:

s = 'A=5'
d = {name: int(value) for name, value in s.split('=')}
ValueError: need more than 1 value to unpack

Why can't I unpack the tuple when it's in a dict comprehension? If I get this working then I can easily make the whole function into a single compact dict comprehension.

Wooble
  • 87,717
  • 12
  • 108
  • 131
Benjamin Hodgson
  • 42,952
  • 15
  • 108
  • 157

8 Answers8

22

In your code, s.split('=') will return the list: ['A', '5']. When iterating over that list, a single string gets returned each time (the first time it is 'A', the second time it is '5') so you can't unpack that single string into 2 variables.

You could try: for name,value in [s.split('=')]

More likely, you have an iterable of strings that you want to split -- then your dict comprehension becomes simple (2 lines):

 splitstrs = (s.split('=') for s in list_of_strings) 
 d = {name: int(value) for name,value in splitstrs }

Of course, if you're obsessed with 1-liners, you can combine it, but I wouldn't.

mgilson
  • 300,191
  • 65
  • 633
  • 696
9

Sure you could do this:

>>> s = 'A=5, b=7'
>>> {k: int(v) for k, v in (item.split('=') for item in s.split(','))}
{'A': 5, ' b': 7}

But in this case I would just use this more imperative code:

>>> d = {}
>>> for item in s.split(','):
        k, v = item.split('=')
        d[k] = int(v)


>>> d
{'A': 5, ' b': 7}
jamylak
  • 128,818
  • 30
  • 231
  • 230
6

Some people tend to believe you'll go to hell for using eval, but...

s = 'A=5, b=7'
eval('dict(%s)' % s)

Or better, to be safe (thanks to mgilson for pointing it out):

s = 'A=5, b=7'
eval('dict(%s)' % s, {'__builtins__': None, 'dict': dict})
hochl
  • 12,524
  • 10
  • 53
  • 87
sloth
  • 99,095
  • 21
  • 171
  • 219
  • 1
    You could make it a little safer: `eval('dict(%s)'%s,{'__builtins__':None,'dict':dict})` -- I think using this will close the vulnerabilities from injection type attacks (although I'd love to hear via a (preferably friendly) comment if I'm wrong about this). – mgilson Aug 23 '12 at 15:27
3

See mgilson answer to why the error is happening. To achieve what you want, you could use:

d = {name: int(value) for name,value in (x.split('=',1) for x in s.split(','))}

To account for spaces, use .strip() as needed (ex.: x.strip().split('=',1)).

mgibsonbr
  • 21,755
  • 7
  • 70
  • 112
2

How about this code:

a="A=5, b=9"
b=dict((x, int(y)) for x, y in re.findall("([a-zA-Z]+)=(\d+)", a))
print b

Output:

{'A': 5, 'b': 9}

This version will work with other forms of input as well, for example

a="A=5 b=9 blabla: yyy=100"

will give you

{'A': 5, 'b': 9, 'yyy': 100}
hochl
  • 12,524
  • 10
  • 53
  • 87
2
>>> strs='A=5, b=7'

>>> {x.split('=')[0].strip():int(x.split('=')[1]) for x in strs.split(",")}
{'A': 5, 'b': 7}

for readability you should use normal for-in loop instead of comprehensions.

strs='A=5, b=7'
dic={}
for x in strs.split(','):
  name,val=x.split('=')
  dic[name.strip()]=int(val)
Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
1

Since Python 3.8, you can use walrus operator (:=) for this kind of operation. It allows to assign variables in the middle of expressions (in this case, assign the list created by .split('=') to kv).

s = 'A=5, b=7'
{(kv := item.split('='))[0]: int(kv[1]) for item in s.split(', ')}
# {'A': 5, 'b': 7}

One feature is that it leaks the assigned variable, kv, outside the scope it was defined in. If you want to avoid that, you can use a nested for-loop where the inner loop is over a singleton list (as suggested in mgilson's answer).

{k: int(v) for item in s.split(', ') for k,v in [item.split('=')]}

Since Python 3.9, loops over singleton lists are optimized to be as fast as simple assignments, i.e. y in [expr] is as fast as y = expr.

cottontail
  • 10,268
  • 18
  • 50
  • 51
0

How about this?

>>> s
'a=5, b=3, c=4'
>>> {z.split('=')[0].strip(): int(z.split('=')[1]) for z in s.split(',')}
{'a': 5, 'c': 4, 'b': 3}
Burhan Khalid
  • 169,990
  • 18
  • 245
  • 284