urllib.urlencode不喜欢unicode值:这个解决方法怎么样?

如果我有一个像这样的对象:

d = {'a':1, 'en': 'hello'} 

然后我可以把它传递给urllib.urlencode ,没问题:

 percent_escaped = urlencode(d) print percent_escaped 

但是,如果我尝试传递一个types为unicode的值的对象,游戏结束:

 d2 = {'a':1, 'en': 'hello', 'pt': u'olá'} percent_escaped = urlencode(d2) print percent_escaped # This fails with a UnicodeEncodingError 

所以我的问题是关于准备传递给urlencode的对象的可靠方法。

我想出了这个函数,我简单地遍历对象,并编码string或unicodetypes的值:

 def encode_object(object): for k,v in object.items(): if type(v) in (str, unicode): object[k] = v.encode('utf-8') return object 

这似乎工作:

 d2 = {'a':1, 'en': 'hello', 'pt': u'olá'} percent_escaped = urlencode(encode_object(d2)) print percent_escaped 

并且输出a=1&en=hello&pt=%C3%B3la ,准备传递给POST调用或其他任何东西。

但是我的encode_object函数对我来说看起来很不稳定。 首先,它不处理嵌套的对象。

另一方面,如果声明我很紧张。 还有其他什么types的,我应该考虑?

并且正在比较类似于本地对象的type() ,像这样的好习惯?

 type(v) in (str, unicode) # not so sure about this... 

谢谢!

你确实应该紧张。 在一些数据结构中,你可能混合了字节和文本的整个想法是可怕的。 它违背了使用string数据的基本原理:在input时解码,在Unicode中工作,在输出时编码。

更新回应评论:

您即将输出某种HTTP请求。 这需要准备一个字节string。 urllib.urlencode不能正确地准备这个字节的string,如果在你的字典中有序列> = 128的Unicode字符的话是不幸的。 如果你的字典中有字节串和Unicodestring的混合,你需要小心。 让我们来看一下urlencode()的作用:

 >>> import urllib >>> tests = ['\x80', '\xe2\x82\xac', 1, '1', u'1', u'\x80', u'\u20ac'] >>> for test in tests: ... print repr(test), repr(urllib.urlencode({'a':test})) ... '\x80' 'a=%80' '\xe2\x82\xac' 'a=%E2%82%AC' 1 'a=1' '1' 'a=1' u'1' 'a=1' u'\x80' Traceback (most recent call last): File "<stdin>", line 2, in <module> File "C:\python27\lib\urllib.py", line 1282, in urlencode v = quote_plus(str(v)) UnicodeEncodeError: 'ascii' codec can't encode character u'\x80' in position 0: ordinal not in range(128) 

最后两个testing演示urlencode()的问题。 现在我们来看看strtesting。

如果你坚持有一个混合,那么你至less应该确保str对象用UTF-8编码。

'\ x80'是可疑的 – 它不是any_valid_unicode_string.encode('utf8')的结果。
'\ xe2 \ x82 \ xac'确定; 这是u'\ u20ac'.encode('utf8')的结果。
'1'是好的 – input到urlencode()的所有ASCII字符都是可以的,如果需要的话,将会进行百分比编码,比如'%'。

这是一个build议的转换器function。 它不会改变input字典并返回它(像你的那样); 它返回一个新的字典。 如果一个值是一个str对象,但是不是一个有效的UTF-8string,它会强制一个exception。 顺便说一句,你关心它不处理嵌套的对象有点误导 – 你的代码只能用于字典,嵌套的字典的概念并不真正飞行。

 def encoded_dict(in_dict): out_dict = {} for k, v in in_dict.iteritems(): if isinstance(v, unicode): v = v.encode('utf8') elif isinstance(v, str): # Must be encoded in UTF-8 v.decode('utf8') out_dict[k] = v return out_dict 

这里是输出,使用相同的testing以相反的顺序(因为讨厌的这次是在前面):

 >>> for test in tests[::-1]: ... print repr(test), repr(urllib.urlencode(encoded_dict({'a':test}))) ... u'\u20ac' 'a=%E2%82%AC' u'\x80' 'a=%C2%80' u'1' 'a=1' '1' 'a=1' 1 'a=1' '\xe2\x82\xac' 'a=%E2%82%AC' '\x80' Traceback (most recent call last): File "<stdin>", line 2, in <module> File "<stdin>", line 8, in encoded_dict File "C:\python27\lib\encodings\utf_8.py", line 16, in decode return codecs.utf_8_decode(input, errors, True) UnicodeDecodeError: 'utf8' codec can't decode byte 0x80 in position 0: invalid start byte >>> 

这有帮助吗?

看起来这是一个比看起来更广泛的话题,特别是当你需要处理更复杂的字典值时。 我find了解决问题的三种方法:

  1. 修补urllib.py以包含编码参数:

     def urlencode(query, doseq=0, encoding='ascii'): 

    并将所有str(v)转换replace为v.encode(encoding)

    显然不好,因为它很难再分配,更难维护。

  2. 按照此处所述更改默认的Python编码。 博客的作者非常清楚地描述了这个解决scheme的一些问题,谁知道他们可能在阴影中潜伏的更多。 所以对我来说也不好看

  3. 所以,我个人最终以这种可憎的方式结束了,它把任何Unicodestring都编码成任何(合理)复杂结构的UTF-8字节string:

     def encode_obj(in_obj): def encode_list(in_list): out_list = [] for el in in_list: out_list.append(encode_obj(el)) return out_list def encode_dict(in_dict): out_dict = {} for k, v in in_dict.iteritems(): out_dict[k] = encode_obj(v) return out_dict if isinstance(in_obj, unicode): return in_obj.encode('utf-8') elif isinstance(in_obj, list): return encode_list(in_obj) elif isinstance(in_obj, tuple): return tuple(encode_list(in_obj)) elif isinstance(in_obj, dict): return encode_dict(in_obj) return in_obj 

    你可以像这样使用它: urllib.urlencode(encode_obj(complex_dictionary))

    为了对密钥进行编码, out_dict[k]可以用out_dict[k.encode('utf-8')]replace,但对我来说有点太过分了。

看来你不能传递一个Unicode对象到urlencode,所以,在调用它之前,你应该编码每一个unicode对象的参数。 如何以合适的方式做到这一点似乎非常依赖于上下文,但在代码中,您应该始终知道何时使用unicode python对象(unicode表示forms)以及何时使用编码对象(bytestring)。

此外,编码的str值是“多余的”: 编码/解码有什么区别?

我和德国人“Umlaute”有同样的问题。 解决scheme非常简单:

urlencode允许指定编码:

 from urllib import urlencode args = {} args = {'a':1, 'en': 'hello', 'pt': u'olá'} urlencode(args, 'utf-8') >>> 'a=1&en=hello&pt=ol%3F' 

除了指出urlencodealgorithm没有什么棘手之外,没有什么新增的。 而不是一次处理你的数据,然后调用urlencode,这样做是完全没问题的:

 from urllib import quote_plus def urlencode_utf8(params): if hasattr(params, 'items'): params = params.items() return '&'.join( (quote_plus(k.encode('utf8'), safe='/') + '=' + quote_plus(v.encode('utf8'), safe='/') for k, v in params)) 

查看urllib模块(Python 2.6)的源代码,他们的实现不会做更多。 有一个可选的function,其中参数本身是2元组的值被转换成单独的键值对,这有时是有用的,但如果你知道你不需要,上述将做。

你甚至可以摆脱if hasattr('items', params):如果你知道你不需要处理2元组和列表的列表。

我用这个add_get_to_url()方法解决了它:

 import urllib def add_get_to_url(url, get): return '%s?%s' % (url, urllib.urlencode(list(encode_dict_to_bytes(get)))) def encode_dict_to_bytes(query): if hasattr(query, 'items'): query=query.items() for key, value in query: yield (encode_value_to_bytes(key), encode_value_to_bytes(value)) def encode_value_to_bytes(value): if not isinstance(value, unicode): return str(value) return value.encode('utf8') 

特征:

  • “get”可以是字典或(键,值)对的列表
  • 订单不会丢失
  • 值可以是整数或其他简单的数据types。

反馈欢迎。

这一行工作正常在我的情况 – >

 urllib.quote(unicode_string.encode('utf-8')) 

谢谢@IanCleland和@PavelVlasov

为什么这么长的答案?

urlencode(unicode_string.encode('utf-8'))