【问题标题】:urllib.urlencode doesn't like unicode values: how about this workaround?urllib.urlencode 不喜欢 unicode 值:这个解决方法怎么样?
【发布时间】:2011-09-22 18:28:15
【问题描述】:

如果我有这样的对象:

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 的对象的可靠方法。

我想出了这个函数,我只需遍历对象并编码字符串或 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 函数对我来说看起来真的很不稳定。一方面,它不处理嵌套对象。

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

并且正在将某些东西的type() 与本机对象进行比较,就像这种好习惯?

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

谢谢!

【问题讨论】:

  • 应该是u'olá' 而不是u'óla'...
  • PortugueseEncodingError,对不起:)
  • 只是对你最后一句话的想法:应该是if isinstance(v, unicode)而不是if type(v) in ...,也不需要匹配str,因为它不需要编码
  • 谢谢,采纳您的建议。
  • 我会讽刺地回答sys.setdefaultencoding("utf-8"),但我担心我会失去我的所有代表并吊销我的python徽章。

标签: python unicode urlencode


【解决方案1】:

我对德语“元音变音”也有同样的问题。 解决方法很简单:

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

【讨论】:

  • 谢谢!是的,该解决方案也适用于我,并且显然比这里的所有其他解决方案要简单得多。
  • 当心!在 python 2 中,urlencode 的可选第二个参数是doseq。在 python 2.7 中根本没有 encoding arg,但在 python 3 中有。
【解决方案2】:

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

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

感谢@IanCleland 和@PavelVlasov

【讨论】:

    【解决方案3】:

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

    欢迎反馈。

    【讨论】:

      【解决方案4】:

      似乎这是一个比看起来更广泛的话题,尤其是当您必须处理更复杂的字典值时。我找到了 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')],但对我来说有点太多了。

      【讨论】:

      【解决方案5】:

      为什么这么长的答案?

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

      【讨论】:

      • 也许你的意思是urllib.quoteurlencode 接受序列或字典,而不是字符串。
      • 这一行在我的情况下工作正常 urllib.quote(unicode_string.encode('utf-8'))
      【解决方案6】:

      除了指出 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):

      【讨论】:

      • 'float' object has no attribute 'encode' 可能会失败
      【解决方案7】:

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

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

      【讨论】:

      • @pat, @Javier:编码 str 值可能是多余的,但它至少会清除无法解码为 UTF-8 的 str 值。试试'\x80'.encode('utf8') 看看我的意思。什么都不做反而会让你面临错误。您应该明确检查您的 str 值 - 请参阅我更新的答案。
      • @John:我同意明确检查 str 值在您正在使用的编码中是否可解码(在这种情况下为 UTF-8)。但是,我认为您应该测试使用 decode (正如您在解决方案中提出的那样)以明确表达预期的行为。感谢您的澄清。
      【解决方案8】:

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

      根据评论更新:

      您即将输出某种 HTTP 请求。这需要准备为字节字符串。如果 dict 中有序数 >= 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
      >>>
      

      这有帮助吗?

      【讨论】:

      • 我不明白这部分:如果 v 是 str(不是 unicode),那么你做 v.decode('utf8')。结果是一个很好的 unicode 字符串。但这正是urlencode() 不支持的。你能理解我的担心吗? ...啊,你甚至放弃了返回值。这就像一个断言语句......也许。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-06-15
      • 2010-11-20
      • 1970-01-01
      • 1970-01-01
      • 2012-06-11
      • 2011-05-17
      • 1970-01-01
      相关资源
      最近更新 更多