问与答 python-urllib.urlencode不喜欢unicode值:这种解决方法如何?

payne · 2020-02-22 12:30:05 · 热度: 65

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

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

...然后我可以将其传递给type(),没问题:

percent_escaped = urlencode(d)
print percent_escaped

但是,如果我尝试传递值类型为type()的对象,则游戏结束:

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

所以我的问题是有关准备传递给type()的对象的可靠方法。

我想到了这个函数,在其中我简单地遍历对象并编码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

然后输出type(),准备传递给POST调用或其他任何东西。

但是我的type()函数对我来说真的很不稳定。 一方面,它不处理嵌套对象。

另一方面,我对if语句感到紧张。 我还应该考虑其他类型吗?

并将这种东西type()与本机对象进行比较,就像这样的好习惯吗?

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

谢谢!

猜你喜欢:
共收到 8 条回复
gerald #1 · 2020-02-22 12:30:05

您确实应该紧张。 在某些数据结构中可能混合使用字节和文本的整个想法令人震惊。 它违反了处理字符串数据的基本原理:在输入时解码,仅在unicode中工作,在输出时编码。

更新以回应评论:

您将要输出某种HTTP请求。 这需要准备为字节字符串。 如果您的字典中包含序数> = 128的Unicode字符,则urllib.urlencode无法正确准备该字节字符串的事实确实很不幸。 如果您的字典中混用了字节字符串和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'是可以的-输入urlencode()时,所有ASCII字符都可以,如果需要,它将进行百分比编码,例如'%'。

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

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

有帮助吗?

quintin #2 · 2020-02-22 12:30:06

我对德语“ Umlaute”也有同样的问题。解决方案非常简单:

在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'
ernest #3 · 2020-02-22 12:30:07

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

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

    out_dict[k]

    并将所有out_dict[k]转换替换为out_dict[k.encode('utf-8')]

    显然不好,因为它几乎无法重新分配,甚至更难维护。

  2. 如此处所述更改默认的Python编码。 该博客的作者很清楚地描述了此解决方案的一些问题,并且谁知道其中更多的问题可能会潜伏在阴影中。 所以这对我也不好。

  3. 因此,我个人以这种可恶结束,将所有unicode字符串编码为任何(合理的)复杂结构的UTF-8字节字符串:

    out_dict[k]

    您可以像这样使用它:out_dict[k]

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

baldie #4 · 2020-02-22 12:30:08

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

而且,对str值进行编码是“多余的”:编码/解码之间有什么区别?

gerald #5 · 2020-02-22 12:30:09

除了指出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元组和字典列表,则甚至可以摆脱if hasattr('items', params):

jackie #6 · 2020-02-22 12:30:10

我用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”可以是字典或(键,值)对的列表
  • 订单不丢失
  • 值可以是整数或其他简单数据类型。

欢迎反馈。

marcellus #7 · 2020-02-22 12:30:12

在我的情况下,这一行工作正常->

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

谢谢@IanCleland和@PavelVlasov

saber #8 · 2020-02-22 12:30:13

为什么回答那么长?

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

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册