为 Django 创建一个保留顺序的多值字典

Creating an order-preserving multi-value dict for Django

当尝试创建一个保留 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.

TLDR: Re-implement 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 也应该是 re-implemented。


问题是 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()

在Python2中,OrderedDict在pure-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__,并且 returns 仅选取单个值,而不是整个列表。

这个问题在 Python 3.5+ 的大多数构建中都被回避了,因为 OrderedDict 通常有一个 C-implementation 可用,意外地屏蔽了它的方法以防止使用被覆盖的方法。

collections.OrderedDict is now implemented in C, which makes it 4 to 100 times faster.[What's new in Python 3.5]