url文字列とクエリパラメータの組み立てについて

ありがちな書き方

import urllib


def build_url(base_url, params=None):
    if params is None:
        params = {}
    return base_url + '?' + urllib.urlencode(params)

ケースによっては問題がある

# 正常に処理できるパターン
print build_url("http://example.com",
                {'foo': 'bar', 'hoge': 'fuga'})
# => http://example.com?foo=bar&hoge=fuga

# パラメタが渡されないと?だけが末尾についてしまう
print build_url("http://example.com")
# => http://example.com?

# そもそものurlにクエリパラメータが付いていると、?が2度出力されてしまう
print build_url("http://example.com?id=1",
                {'foo': 'bar', 'hoge': 'fuga'})
# => http://example.com?id=1?foo=bar&hoge=fuga

# ハッシュフラグメントの後ろにクエリパラメータがついてしまう
print build_url("http://example.com#new",
                {'foo': 'bar', 'hoge': 'fuga'})
# http://example.com#new?foo=bar&hoge=fuga


以下のように書けば、問題のいくつかは対処できる。
すべてのケースで期待通り動くかどうかはわからん。

import urlparse
import urllib


def build_url2(base_url, params=None):
    if params is None:
        params = {}
    base_url_parts = list(urlparse.urlparse(base_url))
    base_url_query_dict = dict(urlparse.parse_qsl(base_url_parts[4]))
    base_url_query_dict.update(params)
    base_url_parts[4] = urllib.urlencode(base_url_query_dict)
    return urlparse.urlunparse(base_url_parts)


print build_url2("http://example.com",
                 {'foo': 'bar', 'hoge': 'fuga'})
# => http://example.com?foo=bar&hoge=fuga

print build_url2("http://example.com")
# => http://example.com

print build_url2("http://example.com?id=1",
                 {'foo': 'bar', 'hoge': 'fuga'})
# => http://example.com?foo=bar&id=1&hoge=fuga

print build_url2("http://example.com#new",
                 {'foo': 'bar', 'hoge': 'fuga'})
# = >http://example.com?foo=bar&hoge=fuga#new


djangoの場合は、parse_qslのかわりにdjango.http.request.QueryDictを使えばよりよいのではないかと思う