NB: None of the suggested answers work for me as they are for-loop driven (which I already have working) and don't explain what is wrong with the list comprehension version.
I am trying to transform
('name:80', 'desc:100')
into
{'name': 80, 'desc': 100}
A working for-loop:
new_wrap = {}
for item in wrap:
k, v = item.split(':')
new_wrap[k] = int(v)
wrap = new_wrap
a non-working list-comprehension:
wrap = dict([
(k, int(v))
for item in wrap
for (k, v) in item.split(':')
])
which gives
Traceback (most recent call last):
File ..., line ..., in ...
for (k, v) in item.split(':')
ValueError: too many values to unpack
What am I doing wrong?
You don't want to iterate over item.split(':'), you want to unpack it. To do that you can introduce a nested comprehension or generator expression. That gives you an opportunity to create the new variables k and v:
dict([
(k, int(v))
for k,v in (item.split(':') for item in wrap)
])
I would then get rid of the square brackets. It's more efficient to use a generator expression that generates entries on the fly rather than a list comprehension that builds a whole list in memory just to throw it away when the dict is created.
dict((k, int(v))
for k,v in (item.split(':') for item in wrap))
You simplify it even further by using a dict comprehension with curly braces:
{k: int(v) for k,v in (item.split(':') for item in wrap)}
If you would like to stick to a comprehension that's one-level deep and don't mind using Python 3.8+, you can use an assignment expression to hold the result of the split in a variable for indexing:
{(t := s.split(':'))[0]: int(t[1]) for s in wrap}
You should separate splitting from conversion to an int:
{k: int(v) for k, v in [w.split(":") for w in wrap]}
#{'name': 80, 'desc': 100}
But, as roganjosh correctly suggested, you gain nothing from this.
With the insight provided by John Kugelman I was able to figure out the problem and the solution I wanted.
The working code from the for loop:
k, v = item.split(':')
Breakdown:
item.split(':') == 'name:80'.split(':') == 'name', '80'
k, v = 'name', '80'
k == 'name' and v == 80
The non-working code in the list-comp:
for (k, v) in item.split(':')
Breakdown:
item.split(':') == 'name:80'.split(':') == 'name', '80'
for (k, v) in ('name', '80')
And there's the problem: in is not the same as =. in iterates over its arguments, returning one at a time, so:
k, v = 'n', 'a', 'm', 'e' is what is attempted, and because four items are trying to be stuffed into two we get the ValueError: too many values to unpack exception.
The simple solution is to encapsulate the item.split() into a tuple of its own:
for k, v in (item.split(':'), )
which becomes
for k, v in (('name', '80'), )
and since the first item in the tuple is a tuple we get
k, v = 'name', '80' for the first (and only) iteration of the second for loop
and the problem is solved without nesting another list-comp inside the primary list-comp.
The final, working list-comp:
[
(k, int(v))
for item in wrap
for (k, v) in (item.split(':'), )
]
i found this solution:
x = ('name:80', 'desc:100')
dict([tuple(a.split(':')) for a in x])
Related
I have a dict like this:
d = {'first':'', 'second':'', 'third':'value', 'fourth':''}
and I want to find first non-empty value (and it's name, in this example 'third'). There may be more than one non-empty value, but I only want the first one I find.
How can I do this?
Use an OrderedDict which preserves the order of elements. Then loop over them and find the first that isn't empty:
from collections import OrderedDict
d = OrderedDict()
# fill d
for key, value in d.items():
if value:
print(key, " is not empty!")
You could use next (dictionaries are unordered - this somewhat changed in Python 3.6 but that's only an implementation detail currently) to get one "not-empty" key-value pair:
>>> next((k, v) for k, v in d.items() if v)
('third', 'value')
Like this?
def none_empty_finder(dict):
for e in dict:
if dict[e] != '':
return [e,dict[e]]
d = {'first':'', 'second':'', 'third':'value', 'fourth':''}
for k, v in d.items():
if v!='':
return k, v
Edit 1
from the comment if the value is None or '' we better use if v: instead of if v!=''. if v!='' only check the '' and skip others
You can find empty elements and make a list of them:
non_empty_list = [(k,v) for k,v in a.items() if v]
By using list comprehension, you can list all the non-empty values and then fetch the 0th value:
[val for key, val in d.items() if val][0]
I've run across a need to call out to an external executable using the subprocess module. Everything is working fine, however I'd like to improve how I'm generating the commandline arguments.
The executable's command line options require formatting as follows:
--argname=argvalue
I currently have a list of dictionaries as follows:
[{arg1:value1},{arg2:value2}]
What is the best method of expanding these dictionaries into their proper string format? I'm currently iterating over the list, appending to a different list, however it feels there's a more pythonic method I should utilize.
Use items() as in http://docs.quantifiedcode.com/python-code-patterns/readability/not_using_items_to_iterate_over_a_dictionary.html
for key,val in d.items():
print("{} = {}".format(key, val))
' '.join('--{key}={value}'.format(key = k, value = v) for d in arg_list for k, v in d.items())
Essentially, this iterates over each dictionary in the list (for d in arg_list) and then iterates over the items in each dictionary (for k, v in d.items()). Each item is formatted into the proper form, and then all of those key-value pairs are combined.
It is equivalent to:
arg_list = [{arg1:value1},{arg2:value2}]
formatted_args = []
for d in arg_list:
for k, v in d.items():
# Format each item per dictionary
formatted_args.append('--{key}={value}'.format(key = k, value = v))
# Combine the arguments into one string
args_string = ' '.join(formatted_args)
Try this
','.join('{}={}'.format(k, v) for d in arg_list for k, v in d.items())
How about
def format_dict(d):
key = d.keys()[0]
return '--%s=%s' % (key, d[key])
ex = [{'a':'3'}, {'b':'4'}]
print ' '.join(map(format_dict, ex)) # --a=3 --b=4
In Python 3.5, I am trying to iterate over a for loop using dict comprehension, yet it does not seem to work as it does with list. The following will be reported as a syntax error (not by PyCharm, only at runtime):
for k, v in (k, v for k, v in {"invalid": 1, "valid": 2}.items() if k == "valid"): # syntax error
print("Valid: " + (str(v)))
While the following works:
for e in (e for e in ["valid", "invalid"] if e == "valid"): # works
print(e)
I know creating a new dict would work (e.g. ), but I want to avoid the overhead as I am just doing operations on the elements.
for k, v in {k: v for k, v in my_dict.items() if k == "valid"}.items(): # works
print("Valid: " + (str(k)))
Of course, I could use a plain old if k == "valid": continue condition inside the loop, but I would like to understand why dict comprehension seems to be more limited than with list.
Python needs parentheses around the first k, v.
((k, v) for k, v in {"invalid": 1, "valid": 2}.items() if k == "valid")
Otherwise it looks like you're trying to make a tuple with two elements:
k
v for k, v in ...
The second isn't syntactically valid. (It would be if you put parentheses around it, making it a generator expression.)
You need to create a tuple with k, v i.e (k,v):
for k, v in ((k, v) for k, v in {"invalid": 1, "valid": 2}.items() if k == "valid"):
^^^
That is what is causing the syntax error. If you did the same thing with a list, set comprehension etc.. it would be exactly the same, you need to use a container if you want to add more that one element. The second example (e for e... works because you are returning a single element each time, the first two examples are also generator expressions only the latter is actually a dict comp.
I am currently trying to make a function which reverses a dict's keys and values. I was looking online and came across this:
def reverse(d):
return dict([(v, k) for k, v in d.iteritems()])
My problem is that I'm not sure what this means. I understand the idea of a for loop on the single line but I'm not sure how the (v, k) for k, v leads to the keys and values being reversed. Could someone please offer me a hand. (I did search for this, both online and on Stack Overflow but couldn't find anything.)
for k, v in d.iteritems() is each key k and value v so reversing v and k with (v, k) makes the old value the key and the old key the new value
In [7]: d = {1:10,2:20}
In [8]: d.items()
Out[8]: dict_items([(1, 10), (2, 20)]) # tuples of key and value
In [1]: d = {1:10,2:20}
In [2]: for k,v in d.iteritems():
print k,v
...:
1 10 # 1 is the key 10 is the value
2 20
In [3]: new_d = {v:k for k,v in d.iteritems()} # swap key for value and value for key
In [4]: new_d
Out[4]: {10: 1, 20: 2}
Two problems you may encounter are duplicate values or values that are not hashable so they cannot be used as keys like lists, sets etc...
In [5]: d = {1:2,2:2}
In [6]: new_d = {v:k for k,v in d.iteritems()}
In [7]: new_d
Out[7]: {2: 2} # now only one key and value in the dict
In [8]: d = {1:2,2:[2]}
In [9]: new_d = {v:k for k,v in d.iteritems()}
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-9-46a3901ce850> in <module>()
----> 1 new_d = {v:k for k,v in d.iteritems()}
<ipython-input-9-46a3901ce850> in <dictcomp>((k, v))
----> 1 new_d = {v:k for k,v in d.iteritems()}
TypeError: unhashable type: 'list'
dict([(v, k) for k, v in d.iteritems()]) will have the same output as {v:k for k,v in d.iteritems()}, the main difference is the former is also compatible with python < 2.7.
If you were using python < 2.7 there is no need to use a list you can just use a generator expression:
dict((v, k) for k, v in d.iteritems())
the dict constructor can receive an iterable of key/value pairs to create a dictionary, so this code is saying "grab the key/value pairs from this dictionary d and create a new dictionary where the values of d are now the keys and the keys of d become the values"
That is why that the (v,k) are reversed, if you did NOT reverse them, like this
def reverse(d):
return dict([(k, v) for k, v in d.iteritems()])
you would get an identical dictionary back.
also note that in python 2.7 and later you can actually use the even more compact:
{v:k for k,v in d.items()}
Which reads more intuitively (at least to me) because it looks more like a list comprehension, only it creates a dict.
OK, so when you call iteritems() on a dict, it gives you a (key, value) tuple for each item in your dictionary:
for item in d.iteritems():
print(item)
Then you can assign each item in the tuple to a separate variable using Python's
tuple unpacking syntax:
a, b = (1, 2)
print(a) # 1
print(b) # 2
And if you pass a list of tuples to dict(), it treats them as a list
of (key, value) items:
eg_dict = dict([(a, 4), (b, 6)])
print(eg_dict)
Finally, the example you posted makes use of Python's list comprehension
syntax:
item_list = ['item' + str(n) for n in range(1, 6)]
print(item_list)
To understand the code snippet you've posted, you just need to be
familiar with these Python idioms. If you haven't seen any
of these techniques before then it's a fairly dense burst
of new information to get your head around.
I am trying to combine a dictionary comprehension and an inline if statement.
The comprehension loops over all items and as long as the item has not the key id
it creates a new key: job[old_key].
Code
job = {'id':1234, 'age':17, 'name':'dev'}
args = {'job[%s]' % k:v if k != 'id' else k:v for k, v in job}
Wished output
print args
{'id':1234, 'job[age]':17, 'job[name]':'dev'}
A SyntaxError was raised.
args = {'job[%s]' % k:v if k != 'key' else k:v for k, v in job}
^
SyntaxError: invalid syntax
However, when I try to run my script Python complains about k:v.
How can I combine a dictionary comprehension and an inline if statement?
Note: I know that I can easily achieve that task with a for loop, but I just want
to combine these two elements.
The key and value parts are separate expressions. Use the conditional expression in just the key part:
args = {'job[%s]' % k if k != 'id' else k: v for k, v in job.iteritems()}
The : is not part of either expression, only of the dictionary comprehension syntax. You also need to loop over both keys and values; in Python 2, use job.iteritems(), Python 3, job.items().
Demo:
>>> job = {'id':1234, 'age':17, 'name':'dev'}
>>> {'job[%s]' % k if k != 'id' else k: v for k, v in job.iteritems()}
{'id': 1234, 'job[age]': 17, 'job[name]': 'dev'}
Extending the other answer, you can make it more readable as follows
get_key = lambda k: ('job[%s]' if k != 'id' else '%s') % k
args = { get_key(key): val for key, val in job.iteritems()}