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

时间:2011-06-25 21:43:34

标签: python unicode urlencode

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

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

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

percent_escaped = urlencode(d)
print percent_escaped

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

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

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

我想出了这个函数,我只是遍历对象并编码string或unicode类型的值:

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功能对我来说真的很不稳定。首先,它不处理嵌套对象。

另一方面,如果声明,我会很紧张。我还应该考虑其他任何类型吗?

并且正在将某事物的type()与本地对象进行比较,就像这种良好做法一样?

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

谢谢!

8 个答案:

答案 0 :(得分:66)

你应该感到紧张。你可能在某些数据结构中混合使用字节和文本的想法令人恐惧。它违反了使用字符串数据的基本原则:在输入时解码,专门在unicode中工作,在输出时编码。

回复评论时更新:

您即将输出某种HTTP请求。这需要准备为字节字符串。如果你的dict中有ordinal> = 128的unicode字符,urllib.urlencode无法正确准备该字节字符串的事实确实是不幸的。如果你的dict中混合了字节字符串和unicode字符串,你需要小心。我们来看看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)

最后两个测试演示了urlencode()的问题。现在让我们来看看str测试。

如果您坚持使用混合物,那么您至少应该确保str对象以UTF-8编码。

'\ x80'是可疑的 - 它不是any_valid_unicode_string.encode('utf8')的结果。
'\ xe2 \ x82 \ xac'没关系;它是u'\ u20ac'.encode('utf8')的结果。
'1'没问题 - 所有ASCII字符在输入到urlencode()时都是正常的,如果需要,它将进行百分比编码,例如'%'。

这是建议的转换器功能。它不会改变输入字典以及返回它(就像你的那样);它返回一个新的字典。如果值是str对象但不是有效的UTF-8字符串,则会强制执行异常。顺便说一下,你对它没有处理嵌套对象的担心有点误导 - 你的代码只能用dicts工作,而嵌套dicts的概念并没有真正飞行。

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

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

>>> 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
>>>

这有帮助吗?

答案 1 :(得分:10)

我和德语&#34; Umlaute&#34;有同样的问题。 解决方案非常简单:

在Python 3+中,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'

答案 2 :(得分:7)

似乎这是一个比它看起来更广泛的主题,特别是当你必须处理更复杂的字典值时。我发现了3种解决问题的方法:

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

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

    并将所有str(v)次转化替换为v.encode(encoding)

    显然不好,因为它几乎不可再分发,甚至更难维护。

  2. 按照here所述更改默认的Python编码。博客的作者非常清楚地描述了这个解决方案的一些问题,谁知道它们中有多少可能隐藏在阴影中。所以它对我来说也不好看。

  3. 所以我个人最终得到了这种可憎的结果,它将所有unicode字符串编码为任何(合理)复杂结构中的UTF-8字节字符串:

    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')],但对我来说有点太多了。

答案 3 :(得分:5)

您似乎无法将Unicode对象传递给urlencode,因此,在调用它之前,您应该对每个unicode对象参数进行编码。在我看来,如何以正确的方式执行此操作非常依赖于上下文,但在您的代码中,您应始终了解何时使用unicode python对象(unicode表示)以及何时使用编码对象(bytestring)。

此外,对str值进行编码是“多余的”:What is the difference between encode/decode?

答案 4 :(得分:2)

除了指出urlencode算法并不棘手之外,没有什么新东西可以添加。 而不是一次处理你的数据,然后在其上调用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),它们的实现并没有做更多的事情。有一个可选功能,其中参数中的值本身是2元组,它们会变成单独的键值对,这有时很有用,但如果你知道你不需要它,那么上面就可以了。

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

答案 5 :(得分:1)

我用这个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')

特点:

  • &#34;获得&#34;可以是字典或(键,值)对的列表
  • 订单不会丢失
  • 值可以是整数或其他简单数据类型。

欢迎反馈。

答案 6 :(得分:-1)

这一行在我的情况下正常工作 - &gt;

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

感谢@IanCleland和@PavelVlasov

答案 7 :(得分:-4)

为什么这么长的答案?

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