为Django创建保留订单的多值字典

时间:2019-05-13 20:49:28

标签: python django dependency-injection multiple-inheritance super

在尝试创建保留QueryDict子类的交叉兼容订单时:

from collections import OrderedDict

from django.http import QueryDict
from django.conf import settings

settings.configure()

class OrderedQueryDict(QueryDict, OrderedDict):
    pass

querystring = 'z=33&x=11'
print(QueryDict(querystring).urlencode())
print(OrderedQueryDict(querystring).urlencode())

Python 3.x上的输出(正确和预期的结果):

z=33&x=11  # or maybe x=11,z=33 on Python<=3.5
z=33&x=11

Python 2.7上的输出(此查询字符串已损坏):

x=11&z=33
z=3&z=3&x=1&x=1

为什么这个想法在Python 3上有效,但在Python 2上无效?

Django v1.11.20。

1 个答案:

答案 0 :(得分:6)

TLDR:重新实现lists

class OrderedQueryDict(QueryDict, OrderedDict):
    def lists(self):
        """Returns a list of (key, list) pairs."""
        return [(key, self.getlist(key)) for key in self]

要获得全部功能,还应该重新实现iterlists


问题是Django的MultiValueDict覆盖__getitem__来仅检索最后一个值,而getlist检索了所有值。这隐式依赖于基础映射的其他方法,而不使用覆盖的方法。例如,它依靠super().iteritems能够检索值列表:

>>> from django.utils.datastructures import MultiValueDict
>>> d = MultiValueDict({"k": ["v1", "v2"]})
>>> d.items()
[('k', 'v2')]
>>> super(MultiValueDict, d).items()
[('k', ['v1', 'v2'])]

original code使用six覆盖Python 2和3。这是Python 2执行的操作:

def lists(self):
    return list(self.iterlists())

def iterlists(self):
    """Yields (key, list) pairs."""
    return super(MultiValueDict, self).iteritems()

在Python 2中,OrderedDict是用纯Python实现的,并且依赖于self[key]__getitem__来检索值:

def iteritems(self):
    'od.iteritems -> an iterator over the (key, value) pairs in od'
    for k in self:
        yield (k, self[k])

这样,它会从MRO中提取覆盖的__getitem__,并且仅返回单个值,而不返回整个列表。

在大多数Python 3.5+版本中,此问题已被规避,因为OrderedDict通常具有可用的C实现,从而意外地屏蔽了其方法以免使用覆盖的方法。

  

collections.OrderedDict现在用C实现,这使其速度提高了4到100倍。[What's new in Python 3.5]