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

wim*_*wim 7 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())
Run Code Online (Sandbox Code Playgroud)

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

z=33&x=11  # or maybe x=11,z=33 on Python<=3.5
z=33&x=11
Run Code Online (Sandbox Code Playgroud)

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

x=11&z=33
z=3&z=3&x=1&x=1
Run Code Online (Sandbox Code Playgroud)

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

Django v1.11.20。

Mis*_*agi 6

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]
Run Code Online (Sandbox Code Playgroud)

For full functionality, iterlists should be re-implemented as well.


The problem is that Django's MultiValueDict overwrites __getitem__ to retrieve just the last value, with getlist retrieving all values. This implicitly relies on other methods of the underlying mapping not using overridden methods. For example, it relies on super().iteritems being able to retrieve lists of values:

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

The original code uses six to cover both Python 2 and 3. This is what Python 2 executes:

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

def iterlists(self):
    """Yields (key, list) pairs."""
    return super(MultiValueDict, self).iteritems()
Run Code Online (Sandbox Code Playgroud)

In Python 2, OrderedDict is implemented in pure-Python and relies on self[key], i.e. __getitem__, to retrieve values:

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

As such, it picks up the overridden __getitem__ from the MRO and returns only individual values, not the entire lists.

This problem is sidestepped in most builds of Python 3.5+, since OrderedDict usually has a C-implementation available, accidentally shielding its methods from using overridden ones.

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