63

In python 2.6, the following code:

import urlparse
qsdata = "test=test&test2=test2&test2=test3"
qs = urlparse.parse_qs(qsdata)
print qs

Gives the following output:

{'test': ['test'], 'test2': ['test2', 'test3']}

Which means that even though there is only one value for test, it is still being parsed into a list. Is there a way to ensure that if there's only one value, it is not parsed into a list, so that the result would look like this?

{'test': 'test', 'test2': ['test2', 'test3']}
Shabbyrobe
  • 12,298
  • 15
  • 60
  • 87
  • 13
    isn't it more consistent that all values are list and you do not have to worry if it is a list or a single value, why would you want otherwise? – Anurag Uniyal Jun 21 '09 at 15:30
  • 3
    The HTTP standard means it has to be a list. There don't seem to be a lot of alternatives. – S.Lott Jun 21 '09 at 20:51
  • All of urls I met so far were something like " https:// www.example.com/api/v1/resource?queryA=1&queryB=2". I don't understand why the HTTP standard insists the value should be a "list". I think a string is enough. Could anyone give me an instance, please? – vainquit Dec 26 '20 at 17:06

3 Answers3

166

A sidenote for someone just wanting a simple dictionary and never needing multiple values with the same key, try:

dict(urlparse.parse_qsl('foo=bar&baz=qux'))

This will give you a nice {'foo': 'bar', 'baz': 'qux'}. Please note that if there are multiple values for the same key, you'll only get the last one.

tuomassalo
  • 8,717
  • 6
  • 48
  • 50
  • 2
    Doesn't `parse_qsl()` give you a list of key-value pairs (and not a dict)? – bhootjb Sep 28 '13 at 17:45
  • 21
    @MisterBhoot Yes, that's why I have the `dict(...)` call around it. :) – tuomassalo Sep 30 '13 at 09:32
  • 5
    My bad, sorry. I should start sleeping early now. – bhootjb Sep 30 '13 at 09:39
  • 5
    that doesn't work, it's still a dict of query_params to lists – clayg Dec 23 '19 at 20:04
  • **Note:** The `urlparse` module is renamed to `urllib.parse` in Python 3; hence, [`urllib.parse.parse_qsl()`](https://docs.python.org/3/library/urllib.parse.html). **Most important**, the above approach wouldn't work in a scenario where a query param expects a `list` of values and may appear multiple times in the URL. For instance, this `dict(urllib.parse.parse_qsl('foo=2&bar=7&foo=10'))` would return `{"foo":"10","bar":"7"}` instead of `{"foo":["2","10"],"bar":"7"}`. The approach described in [this answer](https://stackoverflow.com/a/1024164/17865804), however, takes care of that. – Chris Aug 10 '22 at 07:05
30

You could fix it afterwards...

import urlparse
qsdata = "test=test&test2=test2&test2=test3"
qs = dict( (k, v if len(v)>1 else v[0] ) 
           for k, v in urlparse.parse_qs(qsdata).iteritems() )
print qs

However, I don't think I would want this. If a parameter that is normally a list happens to arrive with only one item set, then I would have a string instead of the list of strings I normally receive.

SingleNegationElimination
  • 151,563
  • 33
  • 264
  • 304
0

Expanding on @SingleNegationElimination answer a bit. If you do something like this, you can use only the last instance of a query parameter.

from urllib.parse import parse_qs
qsdata = "test=test&test2=test2&test2=test3"
dict((k, v[-1] if isinstance(v, list) else v)
      for k, v in parse_qs(qsdata).items())

# Returns: {'test': 'test', 'test2': 'test3'}

Or you can retain only the first instance of a url parameter with the following:

from urllib.parse import parse_qs
qsdata = "test=test&test2=test2&test2=test3"
dict((k, v[0] if isinstance(v, list) else v)
      for k, v in parse_qs(qsdata).items())

# Returns: {'test': 'test', 'test2': 'test2'}
Joe J
  • 9,985
  • 16
  • 68
  • 100