【问题标题】:urlencode a multidimensional dictionary in pythonurlencode python 中的多维字典
【发布时间】:2010-10-25 10:51:08
【问题描述】:

如何在 Python 中获取多维字典的 URL 编码版本?不幸的是,urllib.urlencode() 仅适用于单一维度。我需要一个能够递归编码字典的版本。

例如,如果我有以下字典:

{'a': 'b', 'c': {'d': 'e'}}

我想获取如下字符串:

a=b&c[d]=e

【问题讨论】:

    标签: python urlencode


    【解决方案1】:

    好的人。我自己实现的:

    import urllib
    
    def recursive_urlencode(d):
        """URL-encode a multidimensional dictionary.
    
        >>> data = {'a': 'b&c', 'd': {'e': {'f&g': 'h*i'}}, 'j': 'k'}
        >>> recursive_urlencode(data)
        u'a=b%26c&j=k&d[e][f%26g]=h%2Ai'
        """
        def recursion(d, base=[]):
            pairs = []
    
            for key, value in d.items():
                new_base = base + [key]
                if hasattr(value, 'values'):
                    pairs += recursion(value, new_base)
                else:
                    new_pair = None
                    if len(new_base) > 1:
                        first = urllib.quote(new_base.pop(0))
                        rest = map(lambda x: urllib.quote(x), new_base)
                        new_pair = "%s[%s]=%s" % (first, ']['.join(rest), urllib.quote(unicode(value)))
                    else:
                        new_pair = "%s=%s" % (urllib.quote(unicode(key)), urllib.quote(unicode(value)))
                    pairs.append(new_pair)
            return pairs
    
        return '&'.join(recursion(d))
    
    if __name__ == "__main__":
        import doctest
        doctest.testmod()
    

    不过,我很想知道是否有更好的方法来做到这一点。我不敢相信 Python 的标准库没有实现这一点。

    【讨论】:

    • 为什么要这样?这绝不是一种标准格式。带有方括号的东西是 PHP 特有的特性,在大多数其他语言/框架或任何 Web 标准中都不存在。
    • 它可能不是一个纯血统的 RFC 描述的 IETF/W3C 支持的神圣通心粉标准,但它现在如此普遍,以至于它没有包含在 Python 的标准库中确实违反了我的理解。我开发了多种平台和语言的 Web 应用程序,这一直是惯例。这包括基于 Python 的环境,例如 Django:所以不,它不再是 PHP 独有的东西了。
    • 只是让后来来这里的人都知道,这段代码接近工作,但不适用于嵌套级别> 1的对象。因此嵌套级别大于 1 的对象将被引导回哈希的顶部。
    • Eric Lubow:感谢您的错误报告。我刚刚纠正了它,现在应该可以了。
    • 太棒了。昨晚很晚才救了我。有点遗憾的是 urllib/urllib2 和请求之类的包装器本身并不支持这一点。再次感谢您 - 我希望我能投票 10 次。
    【解决方案2】:

    这样的?

    a = {'a': 'b', 'c': {'d': 'e'}}
    
    url = urllib.urlencode([('%s[%s]'%(k,v.keys()[0]), v.values()[0] ) if type(v)==dict else (k,v) for k,v in a.iteritems()])
    
    url = 'a=b&c%5Bd%5D=e'
    

    【讨论】:

    • 不怕。请注意 'a=b&c[d]=e' 和您建议的 'a=b&c%5Bd%5D=e' 之间的区别。转义不应该存在。
    • 然后你将不得不像我一样遍历字典并分别对每个项目进行 urlencode...
    • 这只需要字典中的第一个内部值,但我喜欢你这样做的方式,所以为你 +1 呵呵
    【解决方案3】:

    基于@malaney的代码,我认为下面的代码很好地模拟了PHP函数http_build_query()

    #!/usr/bin/env python3
    
    import urllib.parse
    
    def http_build_query(data):
        parents = list()
        pairs = dict()
    
        def renderKey(parents):
            depth, outStr = 0, ''
            for x in parents:
                s = "[%s]" if depth > 0 or isinstance(x, int) else "%s"
                outStr += s % str(x)
                depth += 1
            return outStr
    
        def r_urlencode(data):
            if isinstance(data, list) or isinstance(data, tuple):
                for i in range(len(data)):
                    parents.append(i)
                    r_urlencode(data[i])
                    parents.pop()
            elif isinstance(data, dict):
                for key, value in data.items():
                    parents.append(key)
                    r_urlencode(value)
                    parents.pop()
            else:
                pairs[renderKey(parents)] = str(data)
    
            return pairs
        return urllib.parse.urlencode(r_urlencode(data))
    
    if __name__ == '__main__':
        payload = {
            'action': 'add',
            'controller': 'invoice',
            'code': 'debtor',
            'InvoiceLines': [
                {'PriceExcl': 150, 'Description': 'Setupfee'},
                {'PriceExcl':49.99, 'Description':'Subscription'}
            ],
            'date': '2016-08-01',
            'key': 'Yikes&ampersand'
        }
        print(http_build_query(payload))
    
        payload2 = [
            'item1',
            'item2'
        ]
        print(http_build_query(payload2))
    

    【讨论】:

      【解决方案4】:

      上述解决方案仅适用于深度

      #!/usr/bin/env python
      
      import sys
      import urllib
      
      def recursive_urlencode(data):
          def r_urlencode(data, parent=None, pairs=None):
              if pairs is None:
                  pairs = {}
              if parent is None:
                  parents = []
              else:
                  parents = parent
      
              for key, value in data.items():
                  if hasattr(value, 'values'):
                      parents.append(key)
                      r_urlencode(value, parents, pairs)
                      parents.pop()
                  else:
                      pairs[renderKey(parents + [key])] = renderVal(value)
      
              return pairs
          return urllib.urlencode(r_urlencode(data))
      
      
      def renderKey(parents):
          depth, outStr = 0, ''
          for x in parents:
              str = "[%s]" if depth > 0 else "%s"
              outStr += str % renderVal(x)
              depth += 1
          return outStr
      
      
      def renderVal(val):
          return urllib.quote(unicode(val))
      
      
      def main():
          print recursive_urlencode(payload)
      
      
      if __name__ == '__main__':
          sys.exit(main())
      

      【讨论】:

      • 这个函数似乎将字符串通过 unicode 传递了两次。所以一个'!'变成 '%25%21' 而不仅仅是 '%21'
      【解决方案5】:

      我认为下面的代码可能是你想要的

      导入 urllib.parse def url_encoder(参数): g_encode_params = {} def _encode_params(参数,p_key=None): 编码参数 = {} 如果是实例(参数,字典): 输入参数: encode_key = '{}[{}]'.format(p_key,key) 编码参数[编码键] = 参数[键] elif isinstance(参数,(列表,元组)): 对于偏移量,枚举中的值(参数): encode_key = '{}[{}]'.format(p_key, offset) 编码参数[编码键] = 值 别的: g_encode_params[p_key] = 参数 对于 encode_params 中的键: 值 = 编码参数 [键] _encode_params(值,键) 如果是实例(参数,字典): 输入参数: _encode_params(参数[键],键) 返回 urllib.parse.urlencode(g_encode_params) 如果 __name__ == '__main__': 参数 = {'name': 'interface_name', 'interfaces': [{'interface': 'inter1'}, {'interface': 'inter2'}]} 打印(url_encoder(参数))

      输出是

      接口%5B1%5D%5Binterface%5D=inter2&name=interface_name&interfaces%5B0%5D%5Binterface%5D=inter1

      看起来像

      接口[1][接口]=inter2&name=interface_name&interfaces[0][接口]=inter1

      PS:你可能想用OrderDict 代替上面的dict

      【讨论】:

        【解决方案6】:

        json.dumps 和 json.loads 呢?

        d = {'a': 'b', 'c': {'d': 'e'}}
        s = json.dumps(d)  # s: '{"a": "b", "c": {"d": "e"}}'
        json.loads(s)  # -> d
        

        【讨论】:

        • 我可能错了,但在我看来,json.dumps 后跟 url-encode 确实是解决问题的好方法。这种跨框架(如 Django 等)的查询字符串的特殊构造随着 json 的广泛使用而发展,因此字符串表示为有效的 json 并不是偶然的。 json.dumps(递归感知)因此是更 Pythonic 的解决方案。最后的 URL 编码是问题的附带问题,应该是跟上编码标准的库的责任,所以最好还是把解决方案留给 urllib。
        【解决方案7】:

        这个简化版怎么样:

        def _clean(value):
            return urllib.quote(unicode(value))
        
        '&'.join([ v for val in [[ "%s[%s]=%s"%(k,ik, _(iv)) 
            for ik, iv in v.items()] if type(v)==dict else ["%s=%s"%(k,_(v))] 
            for k,v in data.items() ] 
            for v in val ])
        

        我同意它不可读,也许使用 itertools.chain 而不是另一个列表推导可以更好地完成列表的扁平化。

        这只会更深 1 级,如果您根据级别添加一些逻辑来管理 N 个“[%s]”,您的可以更深 N 级,但我想没有必要

        【讨论】:

          【解决方案8】:

          如果你想将 python dict/list/nested 转换为 PHP Array,如 urlencoded 字符串。

          在python中,大部分你想转换为urlencoded的数据类型可能是:dictlisttuplenested of them,Like

          a = [1, 2]
          print(recursive_urlencode(a))
          # 0=1&1=2
          
          
          a2 = (1, '2')
          print(recursive_urlencode(a2))
          # 0=1&1=2
          
          
          b = {'a': 11, 'b': 'foo'}
          print(recursive_urlencode(b))
          # a=11&b=foo
          
          
          c = {'a': 11, 'b': [1, 2]}
          print(recursive_urlencode(c))
          # a=11&b[0]=1&b[1]=2
          
          
          d = [1, {'a': 11, 'b': 22}]
          print(recursive_urlencode(d))
          # 0=1&1[a]=11&1[b]=22
          
          
          e = {'a': 11, 'b': [1, {'c': 123}, [3, 'foo']]}
          print(recursive_urlencode(e))
          # a=11&b[0]=1&b[1][c]=123&b[2][0]=3&b[2][1]=foo
          

          https://github.com/Viky-zhang/to_php_post_arr

          附:一些代码来自:https://stackoverflow.com/a/4014164/2752670

          【讨论】:

            【解决方案9】:

            get_encoded_url_params() 函数将字典作为参数并返回字典的 url 编码形式。

            def get_encoded_url_params(d):
                """URL-encode a nested dictionary.
                :param d = dict
                :returns url encoded string with dict key-value pairs as query parameters
                e.g.
                if d = { "addr":{ "country": "US", "line": ["a","b"] },
                        "routing_number": "011100915", "token": "asdf"
                    }
                :returns 'addr[country]=US&addr[line][0]=a&addr[line][1]=b&routing_number=011100915&token=asdf'
                or 'addr%5Bcountry%5D=US&addr%5Bline%5D%5B0%5D=a&addr%5Bline%5D%5B1%5D=b&routing_number=011100915&token=asdf'
                (which is url encoded form of the former using quote_plus())
                """
            
                def get_pairs(value, base):
                    if isinstance(value, dict):
                        return get_dict_pairs(value, base)
                    elif isinstance(value, list):
                        return get_list_pairs(value, base)
                    else:
                        return [base + '=' + str(value)]
                        # use quote_plus() to get url encoded string
                        # return [quote_plus(base) + '=' + quote_plus(str(value))]
            
                def get_list_pairs(li, base):
                    pairs = []
                    for idx, value in enumerate(li):
                        new_base = base + '[' + str(idx) + ']'
                        pairs += get_pairs(value, new_base)
                    return pairs
            
                def get_dict_pairs(d, base=''):
                    pairs = []
                    for key, value in d.items():
                        new_base = key if base == '' else base + '[' + key + ']'
                        pairs += get_pairs(value, new_base)
                    return pairs
            
                return '&'.join(get_dict_pairs(d))
            

            【讨论】:

            • 你能解释一下你的答案吗?
            • @BenjaminUrquhart 函数 get_encoded_url_params() 采用 dict/map 并将键值对作为 url 编码参数。它类似于 urllib.urlencode() 但也适用于原始字典中有字典或列表的情况。我在函数内部的注释中提供了一个示例。
            猜你喜欢
            • 2016-08-05
            • 2012-11-19
            • 2011-05-10
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-04-22
            • 1970-01-01
            相关资源
            最近更新 更多